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

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 (83) 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 +41 -2
  9. package/src/build/digest-native.js +25 -0
  10. package/src/build/effect-invocations.js +51 -0
  11. package/src/build/embedded-assets.as.ts +32 -0
  12. package/src/build/entities-native.js +6 -11
  13. package/src/build/host-extensions.js +254 -0
  14. package/src/build/kv-native.as.ts +331 -0
  15. package/src/build/kv-native.js +27 -0
  16. package/src/build/lifecycle.js +50 -0
  17. package/src/build/native-application-errors.js +159 -0
  18. package/src/build/native-binary-resources.js +30 -0
  19. package/src/build/native-body-forwarding.as.ts +325 -0
  20. package/src/build/native-body-forwarding.js +73 -0
  21. package/src/build/native-dynamic-backends.as.ts +115 -0
  22. package/src/build/native-dynamic-backends.js +52 -0
  23. package/src/build/native-fetch-accounting.as.ts +133 -0
  24. package/src/build/native-fetch-accounting.js +84 -0
  25. package/src/build/native-fetch-bounds.js +47 -0
  26. package/src/build/native-fetch-cohorts.as.ts +126 -0
  27. package/src/build/native-fetch-cohorts.js +65 -0
  28. package/src/build/native-fetch-deadlines.as.ts +169 -0
  29. package/src/build/native-fetch-deadlines.js +36 -0
  30. package/src/build/native-fetch-errors.js +38 -0
  31. package/src/build/native-fetch-headers.js +52 -0
  32. package/src/build/native-fetch-outcomes.js +87 -0
  33. package/src/build/native-fetch-timing.as.ts +74 -0
  34. package/src/build/native-fetch-timing.js +50 -0
  35. package/src/build/native-forwarding-accounting.as.ts +153 -0
  36. package/src/build/native-forwarding-accounting.js +47 -0
  37. package/src/build/native-handle-ownership.js +127 -0
  38. package/src/build/native-header-lookup.js +50 -0
  39. package/src/build/native-http-effects.js +164 -35
  40. package/src/build/native-platform-capabilities.js +815 -220
  41. package/src/build/native-request-body.js +45 -0
  42. package/src/build/native-request-headers.js +93 -0
  43. package/src/build/native-send-error-detail.as.ts +12 -0
  44. package/src/build/native-stdio.js +45 -0
  45. package/src/build/native-string-values.js +143 -0
  46. package/src/build/native-transport-details.as.ts +84 -0
  47. package/src/build/native-value-budget.js +90 -0
  48. package/src/build/package-native-application.js +114 -0
  49. package/src/build/request-budget-source.js +58 -0
  50. package/src/build/request-budget.js +46 -0
  51. package/src/build/response-authority.js +56 -0
  52. package/src/build/s3-body.as.ts +97 -0
  53. package/src/build/s3-native.as.ts +228 -0
  54. package/src/build/s3-native.js +30 -0
  55. package/src/build/schema-admission.js +83 -0
  56. package/src/build/schema-nested-json.js +93 -0
  57. package/src/build/schema-scalar-record.js +41 -0
  58. package/src/build/time-native.js +49 -0
  59. package/src/build/wasm-budget.js +28 -0
  60. package/src/config-api.js +6 -0
  61. package/src/config-schema.json +30 -0
  62. package/src/index.ts +25 -0
  63. package/src/javascript/fetch-adapter.js +3 -0
  64. package/src/javascript/jwt-verifier.js +14 -4
  65. package/src/javascript/logging.js +11 -2
  66. package/src/javascript/package-effects.js +5 -2
  67. package/src/javascript/runtime-host.js +8 -3
  68. package/src/javascript/source-package.js +4 -2
  69. package/src/javascript/target-support-policy.js +20 -3
  70. package/src/javascript/target.js +17 -1
  71. package/src/javascript/test-runtime.js +1 -0
  72. package/src/provider-contract.js +23 -6
  73. package/src/runtime/canonical-api-runtime.js +6 -0
  74. package/src/testing/conditional-kv-host.js +155 -0
  75. package/src/testing/native-dynamic-backends-host.js +39 -0
  76. package/src/testing/native-http-effects-host.js +18 -1
  77. package/src/testing/native-platform-capabilities-host.js +359 -24
  78. package/src/toolchain/body-forwarding.js +25 -0
  79. package/src/toolchain/config.js +16 -0
  80. package/src/toolchain/index.js +49 -5
  81. package/src/toolchain/logging.js +18 -0
  82. package/src/toolchain/native-execution.js +83 -0
  83. package/src/toolchain/s3.js +26 -0
@@ -1,6 +1,23 @@
1
1
  'use strict';
2
2
 
3
+ const { fetchHeadersSource } = require('./native-fetch-headers.js');
4
+
5
+ const dynamicBackends = require('./native-dynamic-backends.js');
6
+
7
+ const { scalarRecordProjectionLines } = require('./schema-scalar-record.js');
8
+ const { hasJsonAdmission, tracksJsonDuplicates, schemaParserDepth, nestedJsonProjectionLines, schemaJsonAdmissionSource, schemaFetchJsonSource } = require('./schema-nested-json.js');
9
+ const { nativeLoggingSource } = require('./native-stdio.js');
10
+ const applicationErrors = require('./native-application-errors.js');
11
+ const effectInvocations = require('./effect-invocations.js');
12
+ const lifecycleBindings = require('./lifecycle.js');
13
+ const hostExtensions = require('./host-extensions.js');
14
+ const packageApplication = require('./package-native-application.js');
15
+ const nativeValueBudget = require('./native-value-budget.js');
16
+ const { schemaNeedsValueProjection } = require('@pulse-compute/wasm-contracts/schema-json/registry');
17
+
3
18
  const crypto = require('node:crypto');
19
+ const { nativeStringAssembly, nativeStringFields, nativeStringConcat, nativeStringTrim, nativeStringIndex, needsNativeValueFailureGuard } = require('./native-string-values.js');
20
+ const conditionalKv = require('./kv-native.js');
4
21
  const fs = require('node:fs');
5
22
  const os = require('node:os');
6
23
  const path = require('node:path');
@@ -12,6 +29,7 @@ const {
12
29
  } = require('@pulse-compute/wasm-contracts/package/package-contract');
13
30
  const {
14
31
  EXPERIMENTAL_NATIVE_SIZE_OPTIMIZATION,
32
+ prepareReportCapture,
15
33
  appendAssemblyScriptOptimizationArgs
16
34
  } = require('@pulse-compute/wasm-build-support/native-optimization');
17
35
 
@@ -77,6 +95,7 @@ function loadGuestToolchain() {
77
95
 
78
96
  const { resolveAsc } = loadBuildSupport();
79
97
  const { generateCanonicalNativeAssemblyScript } = loadNativeGenerator();
98
+ const nativeHandleOwnership = require('./native-handle-ownership.js');
80
99
  const { pulseJwtAssemblyScriptSource } = loadJwtNativeSource();
81
100
  const {
82
101
  GUEST_LINK_STAGE_INVOCATION_VERSION,
@@ -89,25 +108,35 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION = 'pulse.fastly-native-platfor
89
108
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_GENERATOR_VERSION = 'pulse.fastly-native-platform-capabilities-generator.v1';
90
109
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION = 'pulse.fastly-native-platform-capabilities-compiler.v1';
91
110
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ABI_VERSION = 1;
92
- const FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES = 1024 * 1024;
111
+ // Internal compilation budget, not a Fastly service limit. Keep a finite guard
112
+ // while allowing composed applications beyond the original 1 MiB proof fixture.
113
+ const { DEFAULT_FASTLY_MAX_WASM_BYTES, normalizeFastlyMaxWasmBytes, assertFastlyWasmBudget } = require('./wasm-budget.js');
114
+ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES = DEFAULT_FASTLY_MAX_WASM_BYTES;
93
115
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES = 65536;
94
116
  const FASTLY_NATIVE_SIZE_OPTIMIZATION = EXPERIMENTAL_NATIVE_SIZE_OPTIMIZATION;
95
117
 
96
118
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS = Object.freeze([
119
+ ...require('./native-body-forwarding.js').IMPORTS.map(key => Object.freeze(key.split(':'))),
120
+ ...conditionalKv.KV_IMPORTS.map((key) => Object.freeze(key.split(':'))),
121
+ ...require('./s3-native.js').S3_IMPORTS.map((key) => Object.freeze(key.split(':'))),
97
122
  Object.freeze(['fastly_abi', 'init']),
98
123
  Object.freeze(['fastly_http_req', 'body_downstream_get']),
99
124
  Object.freeze(['fastly_http_req', 'method_get']),
100
125
  Object.freeze(['fastly_http_req', 'uri_get']),
101
126
  Object.freeze(['fastly_http_req', 'header_value_get']),
127
+ Object.freeze(['fastly_http_req', 'header_names_get']),
128
+ Object.freeze(['fastly_http_req', 'header_values_get']),
102
129
  Object.freeze(['fastly_http_req', 'new']),
103
130
  Object.freeze(['fastly_http_req', 'method_set']),
104
131
  Object.freeze(['fastly_http_req', 'uri_set']),
105
132
  Object.freeze(['fastly_http_req', 'header_insert']),
133
+ Object.freeze(['fastly_http_req', 'header_append']),
106
134
  Object.freeze(['fastly_http_req', 'send_async']),
107
- Object.freeze(['fastly_http_req', 'pending_req_wait']),
135
+ Object.freeze(['fastly_http_req', 'pending_req_wait_v2']),
108
136
  Object.freeze(['fastly_http_resp', 'new']),
109
137
  Object.freeze(['fastly_http_resp', 'header_append']),
110
138
  Object.freeze(['fastly_http_resp', 'header_value_get']),
139
+ Object.freeze(['fastly_http_resp', 'header_values_get']),
111
140
  Object.freeze(['fastly_http_resp', 'status_get']),
112
141
  Object.freeze(['fastly_http_resp', 'status_set']),
113
142
  Object.freeze(['fastly_http_resp', 'send_downstream']),
@@ -124,10 +153,15 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS = Object.freeze([
124
153
  Object.freeze(['fastly_kv_store', 'lookup_wait_v2']),
125
154
  Object.freeze(['fastly_kv_store', 'insert']),
126
155
  Object.freeze(['fastly_kv_store', 'insert_wait']),
156
+ Object.freeze(['fastly_kv_store', 'list']),
157
+ Object.freeze(['fastly_kv_store', 'list_wait']),
158
+ Object.freeze(['fastly_kv_store', 'delete']),
159
+ Object.freeze(['fastly_kv_store', 'delete_wait']),
127
160
  Object.freeze(['fastly_log', 'endpoint_get']),
128
161
  Object.freeze(['fastly_log', 'write']),
162
+ Object.freeze(['wasi_snapshot_preview1', 'fd_write']),
129
163
  Object.freeze(['wasi_snapshot_preview1', 'clock_time_get'])
130
- ]);
164
+ ].filter(([module, name], index, entries) => entries.findIndex((item) => item[0] === module && item[1] === name) === index));
131
165
 
132
166
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_REQUIRED_IMPORTS = Object.freeze([
133
167
  Object.freeze(['fastly_abi', 'init']),
@@ -148,38 +182,70 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KIND = Object.freeze({
148
182
  'grip.publish': 8,
149
183
  'grip.broadcast': 9,
150
184
  'assets.lookup': 10,
151
- 'jwt.verify': 11
185
+ 'jwt.verify': 11,
186
+ 'jwt.sign': 20,
187
+ 's3.head': 12,
188
+ 's3.getText': 13,
189
+ 's3.putText': 14,
190
+ 's3.getBody': 21,
191
+ 'kv.delete': 22,
192
+ 'kv.list': 23,
193
+ 'kv.getVersioned': 15,
194
+ 'kv.insertIfAbsent': 16,
195
+ 'kv.compareAndSwap': 17,
196
+ 'time.now': 18,
197
+ 'crypto.digestText': 19,
198
+ 'host.extension': 24
152
199
  });
153
200
 
154
201
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ALLOWED_IMPORTS = new Set([
202
+ 'fastly_http_req:register_dynamic_backend',
155
203
  ...FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS.map(([moduleName, name]) => `${moduleName}:${name}`)
156
204
  ]);
157
205
 
158
206
  const FASTLY_NATIVE_PLATFORM_EFFECT_KINDS = Object.freeze([
207
+ 'host.extension',
208
+ 'time.now',
209
+ 'crypto.digestText',
159
210
  'fetch',
160
211
  'config.get',
161
212
  'secret.get',
162
213
  'kv.get',
163
214
  'kv.put',
215
+ ...conditionalKv.KV_CONDITIONAL_KINDS,
164
216
  'grip.channel',
165
217
  'grip.hold',
166
218
  'grip.publish',
167
219
  'grip.broadcast',
168
220
  'assets.lookup',
169
- 'jwt.verify'
221
+ 'jwt.verify',
222
+ 'jwt.sign',
223
+ 's3.head',
224
+ 's3.getText',
225
+ 's3.putText',
226
+ 's3.getBody'
170
227
  ]);
171
228
 
172
229
  const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
230
+ 'host.extension',
231
+ 'time.now',
232
+ 'crypto.digestText',
173
233
  'config.get',
174
234
  'secret.get',
175
235
  'kv.get',
176
236
  'kv.put',
237
+ ...conditionalKv.KV_CONDITIONAL_KINDS,
177
238
  'assets.lookup',
178
239
  'grip.channel',
179
240
  'grip.hold',
180
241
  'grip.publish',
181
242
  'grip.broadcast',
182
- 'jwt.verify'
243
+ 'jwt.verify',
244
+ 'jwt.sign',
245
+ 's3.head',
246
+ 's3.getText',
247
+ 's3.putText',
248
+ 's3.getBody'
183
249
  ]);
184
250
 
185
251
  class FastlyNativePlatformCapabilitiesError extends Error {
@@ -241,45 +307,38 @@ function packageFacts(_plan, options = {}) {
241
307
  });
242
308
  }
243
309
 
310
+ function jwtVerificationAlgorithm(effect) {
311
+ return effect.resource.keyType === 'secret' ? 'HS256' : String(effect.resource.keyArtifactId).startsWith('pulse.jwt.rs256-key.') ? 'RS256' : 'ES256';
312
+ }
313
+
244
314
  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
- );
315
+ const effects = plan.effects || [];
316
+ const signing = effects.filter(effect => effect.kind === 'jwt.sign');
317
+ const verifies = effects.filter(effect => effect.kind === 'jwt.verify');
318
+ const required = new Set([
319
+ ...signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm') === 'HS256' ? 'HMAC-SHA256' : literalObjectField(inputExpression(effect, 'payload'), 'algorithm')),
320
+ ...verifies.map(effect => jwtVerificationAlgorithm(effect))
321
+ ]);
322
+ if (!required.size) return undefined;
323
+ const definitions = {
324
+ 'HMAC-SHA256': ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
325
+ HS256: ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
326
+ RS256: ['guest-linked:pulse-rs256-bearssl-i31', 'bearssl.0.6.rsa-i31.sha256.v1', 'guest-linked'],
327
+ ES256: ['guest-linked:pulse-es256-rustcrypto-p256', 'rustcrypto.p256-0.13.2.ecdsa-0.16.9.sha2-0.10.9.v1', 'guest-linked']
328
+ };
329
+ for (const algorithm of required) {
330
+ const selected = plan.crypto?.algorithms.find(entry => entry.algorithm === algorithm);
331
+ const expected = definitions[algorithm];
332
+ if (!expected || !selected || selected.realization !== expected[0] || selected.implementation !== expected[1]
333
+ || selected.kind !== expected[2] || selected.targetImplemented !== true || selected.automaticFallback !== false) {
334
+ fail('JWT requires its explicitly selected Crypto realization.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
335
+ }
274
336
  }
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
- });
337
+ const verificationAlgorithms = [...new Set(verifies.map(effect => jwtVerificationAlgorithm(effect)))];
338
+ if (verificationAlgorithms.length > 1) fail('Fastly Native JWT verification requires one exact algorithm.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
339
+ const algorithm = verificationAlgorithms[0] || (required.has('RS256') ? 'RS256' : required.has('ES256') ? 'ES256' : 'HMAC-SHA256');
340
+ return Object.freeze({ ...plan.crypto.algorithms.find(entry => entry.algorithm === algorithm),
341
+ guestUnitRequired: required.has('ES256') || required.has('RS256'), signingAlgorithms: signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm')) });
283
342
  }
284
343
 
285
344
  function decodeP256Coordinate(value, artifactId, field) {
@@ -301,6 +360,26 @@ function decodeP256Coordinate(value, artifactId, field) {
301
360
  return bytes;
302
361
  }
303
362
 
363
+ function normalizedRs256Jwk(value, artifactId) {
364
+ const invalid = () => fail('Invalid bounded RSA public key artifact.', 'PULSE_FASTLY_NATIVE_JWT_KEY_ARTIFACT_INVALID', { artifactId });
365
+ if (!value || typeof value !== 'object' || Array.isArray(value)
366
+ || Object.keys(value).some(name => !['kty', 'n', 'e', 'alg', 'use', 'key_ops', 'kid'].includes(name))
367
+ || value.kty !== 'RSA' || value.alg !== undefined && value.alg !== 'RS256'
368
+ || value.use !== undefined && value.use !== 'sig'
369
+ || value.key_ops !== undefined && (!Array.isArray(value.key_ops) || value.key_ops.length !== 1 || value.key_ops[0] !== 'verify')
370
+ || value.kid !== undefined && (typeof value.kid !== 'string' || !value.kid.length || value.kid.includes('\0') || Buffer.byteLength(value.kid) > 256)) invalid();
371
+ const decode = (text, maximum) => {
372
+ if (typeof text !== 'string' || text.length > Math.ceil(maximum * 4 / 3) || !/^[A-Za-z0-9_-]+$/.test(text)) invalid();
373
+ const bytes = Buffer.from(text, 'base64url');
374
+ if (!bytes.length || bytes.length > maximum || bytes[0] === 0 || bytes.toString('base64url') !== text) invalid();
375
+ return bytes;
376
+ };
377
+ const n = decode(value.n, 512), e = decode(value.e, 4), k = n.length;
378
+ if (![256, 384, 512].includes(k) || n[0] < 128 || !(n[k-1] & 1) || e.readUIntBE(0, e.length) < 3 || !(e.at(-1) & 1)) invalid();
379
+ const bytes = Buffer.alloc(8+k); bytes.writeUInt32LE(k); n.copy(bytes, 4); e.copy(bytes, 8+k-e.length);
380
+ return Object.freeze({ kid: value.kid, bytes });
381
+ }
382
+
304
383
  function normalizedEs256Jwk(value, artifactId) {
305
384
  if (
306
385
  !value
@@ -347,13 +426,14 @@ function resolveEs256KeyArtifacts(plan, facts) {
347
426
  }
348
427
  const artifactId = effect.resource.keyArtifactId;
349
428
  const artifact = artifacts.get(artifactId);
429
+ const algorithm = jwtVerificationAlgorithm(effect).toLowerCase();
350
430
  if (
351
431
  !artifact
352
- || artifact.version !== 'pulse.jwt-es256-key-artifact.v1'
432
+ || artifact.version !== `pulse.jwt-${algorithm}-key-artifact.v1`
353
433
  || artifact.contractId !== 'pulse.jwt'
354
434
  || artifact.package !== '@pulse-compute/jwt'
355
- || artifact.kind !== 'jwt-es256-static-public-key'
356
- || artifact.mediaType !== 'application/vnd.pulse.jwt-es256-key+json'
435
+ || artifact.kind !== `jwt-${algorithm}-static-public-key`
436
+ || artifact.mediaType !== `application/vnd.pulse.jwt-${algorithm}-key+json`
357
437
  || artifact.id !== artifactId
358
438
  || artifact.materialHash !== sha256(stableStringify(artifact.data))
359
439
  || !artifact.data
@@ -379,7 +459,7 @@ function resolveEs256KeyArtifacts(plan, facts) {
379
459
  { effectId: effect.id, artifactId, keyCount: keys && keys.length }
380
460
  );
381
461
  }
382
- const normalized = keys.map((key) => normalizedEs256Jwk(key, artifactId));
462
+ const normalized = keys.map((key) => algorithm === 'rs256' ? normalizedRs256Jwk(key, artifactId) : normalizedEs256Jwk(key, artifactId));
383
463
  const kids = normalized.map((key) => key.kid).filter((kid) => kid !== undefined);
384
464
  if (new Set(kids).size !== kids.length) {
385
465
  fail(
@@ -455,8 +535,71 @@ function es256KeyArtifactSource(records) {
455
535
  return lines.join('\n');
456
536
  }
457
537
 
538
+ function jwtSigningCryptoCompositionSource(selected) {
539
+ const algorithms = selected?.signingAlgorithms || [];
540
+ const hs = algorithms.includes('HS256');
541
+ const es = algorithms.includes('ES256');
542
+ const rs = algorithms.includes('RS256');
543
+ return `function __pulse_fastly_jwt_crypto_sign(algorithm: string, key: Uint8Array, data: Uint8Array, output: Uint8Array): i32 {
544
+ ${hs ? `if (algorithm == "HS256") return pulse_crypto_hmac_sha256_bytes_v1(key.dataStart, key.length, data.dataStart, data.length, output.dataStart, output.length)` : ''}
545
+ ${rs ? `if (algorithm == "RS256") return __pulse_fastly_rs256(true, key, data, output)` : ''}
546
+ ${es ? `if (algorithm == "ES256") {
547
+ if (key.length != 96 || output.length != 64 || data.length > 16340) return -2
548
+ const pointer: usize = 524288
549
+ memory.fill(pointer, 0, 16640)
550
+ store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
551
+ store<u32>(pointer + 12, (224 + data.length + 15) & ~15); store<u32>(pointer + 16, 1)
552
+ store<u32>(pointer + 24, 224); store<u32>(pointer + 28, data.length)
553
+ store<u32>(pointer + 32, 64); store<u32>(pointer + 36, 96)
554
+ store<u32>(pointer + 40, 160); store<u32>(pointer + 44, 64)
555
+ memory.copy(pointer + 64, key.dataStart, 96); memory.copy(pointer + 224, data.dataStart, data.length)
556
+ const status = __pulse_crypto_es256_sign_anchor(i32(pointer), 16640)
557
+ if (status == 1) memory.copy(output.dataStart, pointer + 160, 64)
558
+ memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
559
+ return status
560
+ }` : ''}
561
+ return -3
562
+ }`;
563
+ }
564
+
458
565
  function jwtCryptoCompositionSource(selected) {
459
- if (!selected) {
566
+ const rsa = selected && (selected.algorithm === 'RS256' || selected.signingAlgorithms?.includes('RS256'));
567
+ const helper = rsa ? rs256CompositionSource() : '';
568
+ return helper + jwtVerificationCompositionSource(selected);
569
+ }
570
+
571
+ function rs256CompositionSource() {
572
+ return `function __pulse_fastly_rs256(signing: bool, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
573
+ if (key.length < 8 || data.length > 12288) return -2
574
+ const k = load<u32>(key.dataStart)
575
+ if ((k != 256 && k != 384 && k != 512) || signature.length != i32(k) || key.length != i32(signing ? 8 + k * 9 / 2 : 8 + k)) return -2
576
+ const pointer: usize = 524288
577
+ const output = (64 + key.length + 15) & ~15
578
+ const input = output + signature.length
579
+ memory.fill(pointer, 0, 16640)
580
+ store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
581
+ store<u32>(pointer + 12, (input + data.length + 15) & ~15); store<u32>(pointer + 16, 2)
582
+ store<u32>(pointer + 24, input); store<u32>(pointer + 28, data.length)
583
+ store<u32>(pointer + 32, 64); store<u32>(pointer + 36, key.length)
584
+ store<u32>(pointer + 40, output); store<u32>(pointer + 44, k)
585
+ memory.copy(pointer + 64, key.dataStart, key.length); memory.copy(pointer + input, data.dataStart, data.length)
586
+ if (!signing) memory.copy(pointer + output, signature.dataStart, signature.length)
587
+ const status = signing ? __pulse_crypto_rs256_sign_anchor(i32(pointer), 16640) : __pulse_crypto_rs256_verify_anchor(i32(pointer), 16640)
588
+ if (signing && status == 1) memory.copy(signature.dataStart, pointer + output, signature.length)
589
+ memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
590
+ return status
591
+ }\n`;
592
+ }
593
+
594
+ function jwtVerificationCompositionSource(selected) {
595
+ if (selected?.algorithm === 'RS256') return `
596
+ function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { __pulse_fastly_jwt_record_signing_input(input.byteLength, "") }
597
+ function __pulse_fastly_jwt_crypto_verify(algorithm: string, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
598
+ if (algorithm != "RS256") return -2
599
+ return __pulse_fastly_rs256(false, key, data, signature)
600
+ }`;
601
+
602
+ if (!selected || selected.algorithm === 'HMAC-SHA256') {
460
603
  return [
461
604
  'function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { void input }',
462
605
  '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 +684,7 @@ function fastlyGuestLinkedStartWrapperWat() {
541
684
  }
542
685
 
543
686
  function validatePlanBoundary(plan, options = {}) {
687
+ require('./native-body-forwarding.js').validate(plan, options);
544
688
  if (!plan || typeof plan !== 'object') throw new TypeError('Fastly native platform capability realization requires a canonical native plan.');
545
689
  if (plan.version !== CANONICAL_NATIVE_PLAN_VERSION) {
546
690
  fail(`Fastly native platform capability realization requires ${CANONICAL_NATIVE_PLAN_VERSION}.`, 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_PLAN_VERSION_UNSUPPORTED', { version: plan.version });
@@ -548,6 +692,7 @@ function validatePlanBoundary(plan, options = {}) {
548
692
  if (!plan.ownership || plan.ownership.providerNeutral !== true || plan.ownership.javascriptRuntime !== false) {
549
693
  fail('Fastly native platform capability realization requires a provider-neutral plan without JavaScript runtime ownership.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_PLAN_OWNERSHIP_INVALID', { ownership: plan.ownership });
550
694
  }
695
+ const selectedExtensions = hostExtensions.select(plan, options);
551
696
  const effects = plan.effects || [];
552
697
  const unsupported = effects.filter((effect) => !FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.includes(effect.kind));
553
698
  if (unsupported.length > 0) {
@@ -569,13 +714,17 @@ function validatePlanBoundary(plan, options = {}) {
569
714
  && effect.kind === 'assets.lookup';
570
715
  const jwt = effect.package === '@pulse-compute/jwt'
571
716
  && effect.contractId === 'pulse.jwt'
572
- && effect.kind === 'jwt.verify'
573
- && effect.operation === 'verify'
574
- && effect.capability === 'jwt.verify';
575
- return !grip && !assets && !jwt;
717
+ && ['verify', 'sign'].includes(effect.operation)
718
+ && effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
719
+ const s3 = effect.package === '@pulse-compute/s3' && effect.contractId === 'pulse.s3'
720
+ && ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
721
+ const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
722
+ && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
723
+ const extension = effect.kind === 'host.extension' && selectedExtensions.some(item => hostExtensions.matches(effect, item.artifact));
724
+ return !grip && !assets && !jwt && !s3 && !digest && !extension;
576
725
  });
577
726
  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', {
727
+ 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
728
  effects: unsupportedPackages.map((entry) => ({ id: entry.id, kind: entry.kind, package: entry.package, contractId: entry.contractId }))
580
729
  });
581
730
  }
@@ -589,15 +738,19 @@ function validatePlanBoundary(plan, options = {}) {
589
738
  && effect.kind === 'assets.lookup';
590
739
  const jwt = effect.package === '@pulse-compute/jwt'
591
740
  && effect.contractId === 'pulse.jwt'
592
- && effect.kind === 'jwt.verify'
593
- && effect.operation === 'verify'
594
- && effect.capability === 'jwt.verify';
595
- return !grip && !assets && !jwt;
741
+ && ['verify', 'sign'].includes(effect.operation)
742
+ && effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
743
+ const s3 = effect.package === '@pulse-compute/s3' && effect.contractId === 'pulse.s3'
744
+ && ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
745
+ const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
746
+ && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
747
+ const extension = effect.kind === 'host.extension' && selectedExtensions.some(item => hostExtensions.matches(effect, item.artifact));
748
+ return !grip && !assets && !jwt && !s3 && !digest && !extension;
596
749
  });
597
750
  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 });
751
+ 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
752
  }
600
- for (const effect of effects.filter((entry) => entry.kind === 'jwt.verify')) {
753
+ for (const effect of effects.filter((entry) => ['jwt.verify', 'jwt.sign'].includes(entry.kind))) {
601
754
  const resource = effect.resource;
602
755
  const knownShape = resource
603
756
  && !Object.keys(resource)
@@ -612,7 +765,7 @@ function validatePlanBoundary(plan, options = {}) {
612
765
  && typeof resource.keyArtifactId === 'string'
613
766
  && resource.keyArtifactId.length > 0
614
767
  && resource.secretBinding === null;
615
- if (!namedSecret && !staticPublicKey) {
768
+ if (!namedSecret && (!staticPublicKey || effect.kind === 'jwt.sign')) {
616
769
  fail('Fastly Native JWT requires one exact named-secret or bounded static public-key descriptor.', 'PULSE_FASTLY_NATIVE_JWT_KEY_DESCRIPTOR_INVALID', {
617
770
  effectId: effect.id,
618
771
  resource,
@@ -678,8 +831,8 @@ function resolveCapabilityBindings(plan, options = {}) {
678
831
  const effects = plan.effects || [];
679
832
  const hasConfig = effects.some((effect) => effect.kind === 'config.get');
680
833
  const hasSecret = effects.some((effect) => (
681
- effect.kind === 'secret.get'
682
- || (effect.kind === 'jwt.verify' && effect.resource && effect.resource.keyType === 'secret')
834
+ effect.kind === 'secret.get' || ['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind)
835
+ || (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource && effect.resource.keyType === 'secret')
683
836
  ));
684
837
  const hasGripHold = effects.some((effect) => effect.kind === 'grip.hold');
685
838
  const hasGripBroadcast = effects.some((effect) => effect.kind === 'grip.broadcast');
@@ -703,8 +856,9 @@ function resolveCapabilityBindings(plan, options = {}) {
703
856
 
704
857
  const kv = [];
705
858
  for (const [index, effect] of effects.entries()) {
706
- if (effect.kind !== 'kv.get' && effect.kind !== 'kv.put' && effect.kind !== 'assets.lookup') continue;
859
+ if (effect.kind !== 'kv.get' && effect.kind !== 'kv.put' && !conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) && effect.kind !== 'assets.lookup') continue;
707
860
  const packagePayload = inputExpression(effect, 'payload');
861
+ if (effect.kind === 'assets.lookup' && literalObjectField(packagePayload, 'embeddedId')) continue;
708
862
  const logical = effect.resource && effect.resource.kind === 'literal'
709
863
  && effect.kind !== 'assets.lookup'
710
864
  ? String(effect.resource.value)
@@ -715,6 +869,7 @@ function resolveCapabilityBindings(plan, options = {}) {
715
869
  resource: effect.resource
716
870
  });
717
871
  }
872
+ 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
873
  const physical = stringBinding(
719
874
  kvMap[logical],
720
875
  `Fastly KV binding for ${logical}`,
@@ -770,11 +925,11 @@ function resolveCapabilityBindings(plan, options = {}) {
770
925
  backend = byEffect[effect.id]
771
926
  || (origin && byOrigin[origin])
772
927
  || (effect.resource && effect.resource.value && byOrigin[effect.resource.value]);
773
- if ((typeof backend !== 'string' || backend.trim() === '') && !origin) {
928
+ if ((typeof backend !== 'string' || backend.trim() === '') && !origin && source.dynamicBackends !== true) {
774
929
  const configured = [...new Set(Object.values(byOrigin).filter((value) => typeof value === 'string' && value.trim()).map((value) => value.trim()))];
775
930
  if (configured.length === 1) backend = configured[0];
776
931
  }
777
- if (typeof backend !== 'string' || backend.trim() === '') {
932
+ if ((typeof backend !== 'string' || backend.trim() === '') && !(source.dynamicBackends === true && !(plan.capabilities || []).includes('request.body.forward'))) {
778
933
  fail(`Fastly native effect ${effect.id} has no static backend binding.`, 'PULSE_FASTLY_BACKEND_REQUIRED', {
779
934
  effectId: effect.id,
780
935
  effectIndex: index,
@@ -782,7 +937,7 @@ function resolveCapabilityBindings(plan, options = {}) {
782
937
  hint: `Provide bindings.backends[${JSON.stringify(origin || '<origin>')}] or an explicit effect backend.`
783
938
  });
784
939
  }
785
- backend = backend.trim();
940
+ backend = typeof backend === 'string' ? backend.trim() : '';
786
941
  }
787
942
  const binding = Object.freeze({
788
943
  index,
@@ -791,6 +946,7 @@ function resolveCapabilityBindings(plan, options = {}) {
791
946
  origin,
792
947
  url,
793
948
  backend,
949
+ ...(!backend && effect.kind === 'fetch' ? { dynamic: true } : {}),
794
950
  ...(effect.kind === 'grip.broadcast' && gripSecretRef ? { authSecretRef: gripSecretRef } : {})
795
951
  });
796
952
  backends.push(binding);
@@ -799,8 +955,12 @@ function resolveCapabilityBindings(plan, options = {}) {
799
955
  }
800
956
 
801
957
  return Object.freeze({
958
+ maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(options.maxDurationMs),
959
+ bodyForwarding: require('../toolchain/body-forwarding.js').normalizeBodyForwarding(options.bodyForwarding, options.maxDurationMs),
960
+ ...(source.logEndpoint === undefined ? {} : { logEndpoint: require('../toolchain/logging.js').normalizeFastlyLogEndpoint(source.logEndpoint) }),
802
961
  configStore,
803
962
  secretStore,
963
+ 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
964
  kv: Object.freeze(kv),
805
965
  fetch: Object.freeze(fetch),
806
966
  backends: Object.freeze(backends),
@@ -834,7 +994,7 @@ function planRequiresFetchBodyRead(plan) {
834
994
  if (Array.isArray(value)) return value.some(visit);
835
995
  return Object.values(value).some(visit);
836
996
  }
837
- return visit(plan.entry);
997
+ return visit(plan.entry) || visit(plan.handlers) || visit(plan.stages) || visit(plan.helpers);
838
998
  }
839
999
 
840
1000
  function requiredImportsForPlan(plan, bindings) {
@@ -849,33 +1009,45 @@ function requiredImportsForPlan(plan, bindings) {
849
1009
  'fastly_http_body:new',
850
1010
  'fastly_http_body:write'
851
1011
  ]);
1012
+ if (bindings && bindings.maxDurationMs !== undefined) { keys.add('wasi_snapshot_preview1:clock_time_get'); keys.add('fastly_async_io:select'); }
852
1013
  const kinds = new Set((plan.effects || []).map((effect) => effect.kind));
853
1014
  if (kinds.has('fetch') || kinds.has('grip.publish') || kinds.has('grip.broadcast')) {
854
1015
  for (const key of [
855
1016
  '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'
1017
+ 'fastly_http_req:header_append', 'fastly_http_req:send_async', 'fastly_http_req:pending_req_wait_v2'
857
1018
  ]) keys.add(key);
858
1019
  }
1020
+ if (bindings && dynamicBackends.active(bindings)) keys.add('fastly_http_req:register_dynamic_backend');
1021
+ if (plan.effects.some(effect=>effect.fetchTiming)) keys.add('fastly_async_io:is_ready');
1022
+ 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
1023
  if (planRequiresFetchBodyRead(plan)) keys.add('fastly_http_body:read');
860
1024
  if (kinds.has('config.get')) for (const key of ['fastly_config_store:open', 'fastly_config_store:get']) keys.add(key);
861
1025
  const gripAuthenticationRequired = kinds.has('grip.broadcast')
862
1026
  && (!bindings || Boolean(bindings.grip && bindings.grip.authentication));
863
1027
  const jwtSecretRequired = (plan.effects || []).some((effect) => (
864
- effect.kind === 'jwt.verify'
1028
+ ['jwt.verify', 'jwt.sign'].includes(effect.kind)
865
1029
  && effect.resource
866
1030
  && effect.resource.keyType === 'secret'
867
1031
  ));
868
1032
  if (kinds.has('secret.get') || jwtSecretRequired || gripAuthenticationRequired) {
869
1033
  for (const key of ['fastly_secret_store:open', 'fastly_secret_store:get', 'fastly_secret_store:plaintext']) keys.add(key);
870
1034
  }
871
- if (kinds.has('jwt.verify')) keys.add('wasi_snapshot_preview1:clock_time_get');
1035
+ for (const kind of conditionalKv.KV_CONDITIONAL_KINDS) if (kinds.has(kind)) for (const key of conditionalKv.kvImports(kind)) keys.add(key);
1036
+ if (kinds.has('jwt.verify') || kinds.has('jwt.sign') || kinds.has('time.now')) keys.add('wasi_snapshot_preview1:clock_time_get');
872
1037
  if (kinds.has('kv.get') || kinds.has('kv.put') || kinds.has('assets.lookup')) keys.add('fastly_kv_store:open');
873
1038
  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
1039
  if (kinds.has('kv.put')) for (const key of ['fastly_kv_store:insert', 'fastly_kv_store:insert_wait']) keys.add(key);
875
1040
  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');
1041
+ 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))) {
1042
+ if (bindings?.logEndpoint === undefined) keys.add('wasi_snapshot_preview1:fd_write');
1043
+ else { keys.add('fastly_log:endpoint_get'); keys.add('fastly_log:write'); }
1044
+ }
1045
+ 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);
1046
+ if (require('./native-body-forwarding.js').enabled(plan)) {
1047
+ keys.delete('fastly_http_req:header_append'); keys.add('fastly_http_req:header_insert');
1048
+ keys.delete('fastly_http_req:send_async'); keys.delete('fastly_http_req:pending_req_wait_v2');
1049
+ for (const key of require('./native-body-forwarding.js').IMPORTS) keys.add(key);
1050
+ keys.add('fastly_http_body:read');
879
1051
  }
880
1052
  return Object.freeze([...keys].sort());
881
1053
  }
@@ -887,15 +1059,40 @@ function stripPulseHostImports(source) {
887
1059
  .join('\n');
888
1060
  }
889
1061
 
1062
+ function schemaTextEncodeIds(plan) {
1063
+ // Keep the PS3 accounting/failure boundary and all unproved codec families.
1064
+ if (nativeValueBudget.hasBoundedReadLoop(plan)) return [];
1065
+ function closedText(node, depth = 0) {
1066
+ if (depth > 16) return false;
1067
+ if (node.kind === 'string' || node.kind === 'boolean') return true;
1068
+ if (node.kind === 'nullable') return closedText(node.value, depth);
1069
+ if (node.kind === 'array') return closedText(node.element, depth + 1);
1070
+ return node.kind === 'object' && !node.additionalProperties
1071
+ && node.fields.every(field => closedText(field.value, depth + 1));
1072
+ }
1073
+ return ((plan.schemas && plan.schemas.registry && plan.schemas.registry.schemas) || [])
1074
+ .filter(schema => !schema.jsonLimits && schema.root.kind === 'object'
1075
+ && schemaNeedsValueProjection(schema.root) && closedText(schema.root))
1076
+ .map(schema => schema.id);
1077
+ }
1078
+
890
1079
  function generateSchemaRuntime(plan) {
891
1080
  const registry = plan.schemas && plan.schemas.registry ? plan.schemas.registry : { schemas: [], maxBytes: FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES, contentTypePolicy: 'accept-json-or-missing' };
892
1081
  const schemas = registry.schemas || [];
1082
+ const textEncodeIds = new Set(schemaTextEncodeIds(plan));
893
1083
  const nodeLines = [];
1084
+ const scalarProjectors = new Map();
1085
+ const flatObjectProjectors = new Map();
1086
+ const scalarKinds = ['string', 'boolean', 'i32', 'u32', 'f64', 'string-enum'];
894
1087
  let nodeIndex = 0;
895
1088
 
896
1089
  function emitNode(schemaIndex, node) {
897
1090
  const currentIndex = nodeIndex++;
898
1091
  const functionName = `__pulse_fastly_schema_${schemaIndex}_${currentIndex}`;
1092
+ const flatObject = node.kind === 'object' && !node.additionalProperties
1093
+ && node.fields.every(field => scalarKinds.includes(field.value.kind));
1094
+ // Locals belong to this function, so equivalent flat bodies need no node ID.
1095
+ const localIndex = flatObject ? 'flat' : currentIndex;
899
1096
  const child = node.kind === 'nullable'
900
1097
  ? emitNode(schemaIndex, node.value)
901
1098
  : node.kind === 'array'
@@ -904,30 +1101,43 @@ function generateSchemaRuntime(plan) {
904
1101
  const fields = node.kind === 'object'
905
1102
  ? node.fields.map((field) => Object.freeze({ field, apply: emitNode(schemaIndex, field.value) }))
906
1103
  : [];
907
- const body = [`function ${functionName}(valueHandle: i32): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
1104
+ const body = [`function ${functionName}(valueHandle: i32, checkDuplicates: bool): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
908
1105
  if (node.kind === 'nullable') {
909
1106
  body.push(' if (input.kind == PULSE_VALUE_NULL) return valueHandle');
910
- body.push(` return ${child}(valueHandle)`);
1107
+ body.push(` return ${child}(valueHandle, checkDuplicates)`);
911
1108
  } else if (node.kind === 'array') {
912
1109
  body.push(' if (input.kind != PULSE_VALUE_ARRAY) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
913
1110
  body.push(' const output = host_value_array()');
914
1111
  body.push(' for (let index = 0; index < input.values.length; index += 1) {');
915
- body.push(` const item = ${child}(unchecked(input.values[index]))`);
1112
+ body.push(` const item = ${child}(unchecked(input.values[index]), checkDuplicates)`);
916
1113
  body.push(' if (item <= 0) return 0');
917
1114
  body.push(' host_value_array_push(output, item)');
918
1115
  body.push(' }');
919
1116
  body.push(' return output');
1117
+ } else if (node.kind === 'json-value' || node.kind === 'json-object') {
1118
+ body.push(...nestedJsonProjectionLines(node));
1119
+ } else if (node.kind === 'scalar-record') {
1120
+ body.push(...scalarRecordProjectionLines(node));
920
1121
  } else if (node.kind === 'object') {
921
1122
  body.push(' if (input.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 50, -1); return 0 }');
1123
+ if (node.additionalProperties) body.push(' if (checkDuplicates && input.duplicateJsonKeys) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0 }');
922
1124
  body.push(' const output = host_value_object()');
923
1125
  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})`);
1126
+ body.push(` const key_${localIndex}_${fieldIndex} = __pulse_fastly_string_value(${quote(field.name)})`);
1127
+ if (!field.required) body.push(` if (__pulse_fastly_find(input, ${quote(field.name)}) >= 0) {`);
1128
+ body.push(` const field_${localIndex}_${fieldIndex} = host_value_property(valueHandle, key_${localIndex}_${fieldIndex})`);
1129
+ body.push(` if (__pulse_fastly_value(field_${localIndex}_${fieldIndex}).kind == PULSE_VALUE_UNDEFINED) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 51, -1); return 0 }`);
1130
+ body.push(` const projected_${localIndex}_${fieldIndex} = ${apply}(field_${localIndex}_${fieldIndex}, checkDuplicates)`);
1131
+ body.push(` if (projected_${localIndex}_${fieldIndex} <= 0) return 0`);
1132
+ body.push(` host_value_object_set(output, key_${localIndex}_${fieldIndex}, projected_${localIndex}_${fieldIndex})`);
1133
+ if (!field.required) body.push(' }');
930
1134
  });
1135
+ if (node.additionalProperties) {
1136
+ body.push(' for (let index = 0; index < input.keys.length; index++) {', ' const key = unchecked(input.keys[index])');
1137
+ if (node.fields.length) body.push(` if (${node.fields.map(field => `key == ${quote(field.name)}`).join(' || ')}) continue`);
1138
+ body.push(' const item = __pulse_fastly_copy_json(unchecked(input.values[index]), checkDuplicates)',
1139
+ ' if (item <= 0) return 0', ' host_value_object_set(output, __pulse_fastly_string_value(key), item)', ' }');
1140
+ }
931
1141
  body.push(' return output');
932
1142
  } else if (node.kind === 'string') {
933
1143
  body.push(' if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
@@ -952,6 +1162,32 @@ function generateSchemaRuntime(plan) {
952
1162
  throw new TypeError(`Unsupported Fastly native schema node ${String(node.kind)}.`);
953
1163
  }
954
1164
  body.push('}', '');
1165
+ // Only scalar validators preserve the original handle without allocating or
1166
+ // calling child projectors. Compare the exact emitted body, including enum
1167
+ // values/order and error stages.
1168
+ if (scalarKinds.includes(node.kind)) {
1169
+ const key = body.slice(1).join('\n');
1170
+ const existing = scalarProjectors.get(key);
1171
+ if (existing) return existing;
1172
+ const sharedName = `__pulse_fastly_schema_scalar_${scalarProjectors.size}`;
1173
+ scalarProjectors.set(key, sharedName);
1174
+ body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
1175
+ nodeLines.push(...body);
1176
+ return sharedName;
1177
+ }
1178
+ // Share code only after child scalar identities are resolved. Exact bodies
1179
+ // retain field order, presence checks, validation and errors. Each call still
1180
+ // allocates its own output; open/nested/nullable/array bodies remain separate.
1181
+ if (flatObject) {
1182
+ const key = body.slice(1).join('\n');
1183
+ const existing = flatObjectProjectors.get(key);
1184
+ if (existing) return existing;
1185
+ const sharedName = `__pulse_fastly_schema_flat_object_${flatObjectProjectors.size}`;
1186
+ flatObjectProjectors.set(key, sharedName);
1187
+ body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
1188
+ nodeLines.push(...body);
1189
+ return sharedName;
1190
+ }
955
1191
  nodeLines.push(...body);
956
1192
  return functionName;
957
1193
  }
@@ -961,17 +1197,23 @@ function generateSchemaRuntime(plan) {
961
1197
  `const __PULSE_SCHEMA_MAX_BYTES: i32 = ${Number(registry.maxBytes || FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES)}`,
962
1198
  `const __PULSE_SCHEMA_REQUIRE_JSON: bool = ${registry.contentTypePolicy === 'require-json' ? 'true' : 'false'}`,
963
1199
  '',
1200
+ schemaJsonAdmissionSource(plan),
1201
+ fetchHeadersSource(),
1202
+ schemaFetchJsonSource(),
964
1203
  ...nodeLines,
965
- 'function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool): i32 {',
1204
+ `function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool${textEncodeIds.size ? ', textResult: bool = false' : ''}): i32 {`,
966
1205
  ' if (schemaId.length == 0) return valueHandle'
967
1206
  ];
968
1207
  for (const [schemaIndex, schema] of schemas.entries()) {
969
1208
  lines.push(`${schemaIndex === 0 ? ' if' : ' else if'} (schemaId == ${quote(schema.id)}) {`);
970
- lines.push(` const projected = ${roots[schemaIndex]}(valueHandle)`);
1209
+ 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 }`);
1210
+ lines.push(` const projected = ${roots[schemaIndex]}(valueHandle, !encode)`);
971
1211
  lines.push(' if (projected <= 0) return 0');
972
1212
  lines.push(' const input = __pulse_fastly_json(projected, 0)');
973
1213
  lines.push(` const normalized = encode ? __pulse_schema_encode_${schemaIndex}(input) : __pulse_schema_decode_${schemaIndex}(input)`);
974
- lines.push(' return __pulse_fastly_parse_json(normalized)');
1214
+ if (textEncodeIds.has(schema.id)) lines.push(' if (textResult && encode) return __pulse_fastly_string_value(normalized)');
1215
+ if (schema.jsonLimits) lines.push(' const output = __pulse_fastly_parse_json(normalized)', ' if (output > 0) __pulse_fastly_deep_freeze(output)', ' return output');
1216
+ else lines.push(' return __pulse_fastly_parse_json(normalized)');
975
1217
  lines.push(' }');
976
1218
  }
977
1219
  lines.push(' __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1)');
@@ -994,7 +1236,7 @@ function effectResultSource(plan, bindings) {
994
1236
  lines.push(`function __pulse_fastly_secret_store(effectIndex: i32): string { return ${quote(bindings.secretStore)} }`, '');
995
1237
  lines.push('function __pulse_fastly_jwt_secret_binding(effectIndex: i32): string {', ' switch (effectIndex) {');
996
1238
  for (const [index, effect] of (plan.effects || []).entries()) {
997
- if (effect.kind === 'jwt.verify' && effect.resource.keyType === 'secret') {
1239
+ if (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource.keyType === 'secret') {
998
1240
  lines.push(` case ${index}: return ${quote(effect.resource.secretBinding)}`);
999
1241
  }
1000
1242
  }
@@ -1009,12 +1251,31 @@ function effectResultSource(plan, bindings) {
1009
1251
  for (const binding of bindings.grip.publish) lines.push(` case ${binding.index}: return ${quote(binding.authSecretRef || '')}`);
1010
1252
  lines.push(' default: return ""', ' }', '}', '');
1011
1253
 
1254
+ // A config lookup has the same ready-result handoff at every static site.
1255
+ // Keep the site indices in the switch; pass the active index through so the
1256
+ // pending slot and any state error still belong to the original invocation.
1257
+ const configSites = (plan.effects || []).flatMap((effect, index) => effect.kind === 'config.get' ? [index] : []);
1258
+ if (configSites.length > 1) {
1259
+ lines.push('function __pulse_fastly_resolve_config_get(effectIndex: i32): i32 {');
1260
+ lines.push(' const response = __pulse_fastly_wait_ready(effectIndex)');
1261
+ lines.push(' if (response <= 0) return 0');
1262
+ lines.push(' return response');
1263
+ lines.push('}', '');
1264
+ }
1265
+
1012
1266
  lines.push('function __pulse_fastly_resolve_effect(effectIndex: i32): i32 {');
1013
1267
  lines.push(' switch (effectIndex) {');
1014
1268
  for (const [index, effect] of (plan.effects || []).entries()) {
1269
+ if (effect.kind === 'config.get' && configSites.length > 1) {
1270
+ if (index === configSites[0]) {
1271
+ for (const site of configSites) lines.push(` case ${site}:`);
1272
+ lines.push(' return __pulse_fastly_resolve_config_get(effectIndex)');
1273
+ }
1274
+ continue;
1275
+ }
1015
1276
  const result = effect.result || {};
1016
1277
  const decoder = result.decoder;
1017
- const waiter = effect.kind === 'fetch'
1278
+ 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
1279
  ? '__pulse_fastly_wait_fetch'
1019
1280
  : effect.kind === 'grip.publish'
1020
1281
  ? '__pulse_fastly_wait_grip_publish'
@@ -1050,21 +1311,31 @@ function effectResultSource(plan, bindings) {
1050
1311
 
1051
1312
  function effectDispatchSource(plan) {
1052
1313
  const handlerByKind = Object.freeze({
1314
+ 'time.now': '__pulse_fastly_time_begin',
1315
+ 'crypto.digestText': '__pulse_fastly_digest_begin',
1053
1316
  fetch: '__pulse_fastly_fetch_begin',
1054
1317
  'config.get': '__pulse_fastly_config_begin',
1055
1318
  'secret.get': '__pulse_fastly_secret_begin',
1056
1319
  'jwt.verify': '__pulse_fastly_jwt_begin',
1320
+ 'jwt.sign': '__pulse_fastly_jwt_sign_begin',
1321
+ 's3.head': '__pulse_fastly_s3_begin',
1322
+ 's3.getText': '__pulse_fastly_s3_begin',
1323
+ 's3.putText': '__pulse_fastly_s3_begin',
1324
+ 's3.getBody': '__pulse_fastly_s3_begin',
1057
1325
  'kv.get': '__pulse_fastly_kv_get_begin',
1058
1326
  'kv.put': '__pulse_fastly_kv_put_begin',
1327
+ ...Object.fromEntries(conditionalKv.KV_CONDITIONAL_KINDS.map(kind => [kind, '__pulse_fastly_kv_conditional_begin'])),
1059
1328
  'assets.lookup': '__pulse_fastly_assets_begin',
1060
1329
  'grip.channel': '__pulse_fastly_grip_channel_begin',
1061
1330
  'grip.hold': '__pulse_fastly_grip_hold_begin',
1062
1331
  'grip.publish': '__pulse_fastly_grip_publish_begin',
1063
- 'grip.broadcast': '__pulse_fastly_grip_publish_begin'
1332
+ 'grip.broadcast': '__pulse_fastly_grip_publish_begin',
1333
+ 'host.extension': '__pulse_extension_begin'
1064
1334
  });
1065
1335
  const kinds = [...new Set((plan.effects || []).map((effect) => effect.kind))];
1066
1336
  const lines = [
1067
1337
  'function host_effect_begin(effectIndex: i32, payload: i32): void {',
1338
+ ...(needsNativeValueFailureGuard(plan) ? [' if (__pulse_fastly_last_error != PULSE_ERROR_NONE) return'] : []),
1068
1339
  ' 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
1340
  ' const payloadValue = __pulse_fastly_value(payload)',
1070
1341
  ' if (payloadValue.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 47, effectIndex); return }',
@@ -1085,7 +1356,15 @@ function effectDispatchSource(plan) {
1085
1356
  }
1086
1357
 
1087
1358
  function fastlyRuntimeSource(plan, bindings, options = {}) {
1359
+ const lifecycle = options.lifecycle;
1360
+ const textEncodeIds = schemaTextEncodeIds(plan);
1088
1361
  const effectCount = (plan.effects || []).length;
1362
+ const hasAssets = (plan.effects || []).some(effect => effect.kind === 'assets.lookup');
1363
+ 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 }
1364
+ 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) }` : '';
1365
+ 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" }
1366
+ 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])) }
1367
+ 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
1368
  const jwtCrypto = options.jwtCrypto || selectedJwtCrypto(plan);
1090
1369
  const es256KeyRecords = options.es256KeyRecords || Object.freeze([]);
1091
1370
  const jwtReality = options.jwtReality && options.jwtReality.enabled === true
@@ -1143,19 +1422,24 @@ function __pulse_fastly_jwt_attach_evidence(handle: i32): void {
1143
1422
  const value = __pulse_fastly_value(handle)
1144
1423
  if (value.kind == PULSE_VALUE_RESPONSE) __pulse_fastly_jwt_evidence_headers(value)
1145
1424
  }
1146
- function __pulse_fastly_jwt_send_error(): void {
1425
+ `
1426
+ : String.raw`
1427
+ function __pulse_fastly_jwt_attach_evidence(handle: i32): void { void handle }
1428
+ `;
1429
+ // Keep the legacy hook name: body, fetch, header and deadline owners prepend
1430
+ // their specific responses here. Every remaining fatal path needs a response,
1431
+ // independently of whether JWT evidence was requested for this build.
1432
+ const terminalErrorSource = `
1433
+ ${lifecycle?.active ? `function __pulse_fastly_jwt_send_error(): void {\n${lifecycle.site('response-error')}\n __pulse_fastly_jwt_send_error_impl()\n}\n` : ''}function ${lifecycle?.legacyName('__pulse_fastly_jwt_send_error') || '__pulse_fastly_jwt_send_error'}(): void {
1147
1434
  const output = new __PulseFastlyValue()
1148
1435
  output.kind = PULSE_VALUE_RESPONSE
1149
- output.status = 400
1150
- output.text = ""
1436
+ output.status = ${jwtReality ? '__pulse_fastly_jwt_error != 0 ? 400 : 500' : '500'}
1437
+ output.text = output.status == 400 ? "" : "Internal Server Error"
1151
1438
  output.headers.push(new __PulseFastlyHeader("content-type", "text/plain; charset=utf-8"))
1152
- __pulse_fastly_jwt_evidence_headers(output)
1439
+ ${jwtReality ? 'if (__pulse_fastly_jwt_error != 0) __pulse_fastly_jwt_evidence_headers(output)' : ''}
1440
+ // Do not clear the original failure or expose its detail in the response.
1153
1441
  __pulse_fastly_send_result(__pulse_fastly_put(output))
1154
1442
  }
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
1443
  `;
1160
1444
  const gripAuthentication = bindings.grip && bindings.grip.authentication;
1161
1445
  const gripSecretSource = gripAuthentication
@@ -1164,7 +1448,7 @@ function __pulse_fastly_jwt_send_error(): void {}
1164
1448
  const gripAuthorizationSource = gripAuthentication
1165
1449
  ? `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
1450
  : '';
1167
- return String.raw`
1451
+ const source = String.raw`
1168
1452
  @external("fastly_abi", "init") declare function fastly_abi_init(version: i64): i32
1169
1453
  @external("fastly_http_req", "body_downstream_get") declare function fastly_http_req_body_downstream_get(requestOut: usize, bodyOut: usize): i32
1170
1454
  @external("fastly_http_req", "method_get") declare function fastly_http_req_method_get(handle: i32, buffer: usize, bufferLength: i32, writtenOut: usize): i32
@@ -1174,14 +1458,15 @@ function __pulse_fastly_jwt_send_error(): void {}
1174
1458
  @external("fastly_http_req", "method_set") declare function fastly_http_req_method_set(handle: i32, method: usize, methodLength: i32): i32
1175
1459
  @external("fastly_http_req", "uri_set") declare function fastly_http_req_uri_set(handle: i32, uri: usize, uriLength: i32): i32
1176
1460
  @external("fastly_http_req", "header_insert") declare function fastly_http_req_header_insert(handle: i32, name: usize, nameLength: i32, value: usize, valueLength: i32): i32
1461
+ @external("fastly_http_req", "header_append") declare function fastly_http_req_header_append(handle: i32, name: usize, nameLength: i32, value: usize, valueLength: i32): i32
1177
1462
  @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
1463
+ @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
1464
  @external("fastly_http_resp", "new") declare function fastly_http_resp_new(handleOut: usize): i32
1180
1465
  @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
1466
  @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
1182
1467
  @external("fastly_http_resp", "status_get") declare function fastly_http_resp_status_get(handle: i32, statusOut: usize): i32
1183
1468
  @external("fastly_http_resp", "status_set") declare function fastly_http_resp_status_set(handle: i32, status: i32): i32
1184
- @external("fastly_http_resp", "send_downstream") declare function fastly_http_resp_send_downstream(handle: i32, body: i32, streaming: i32): i32
1469
+ @external("fastly_http_resp", "send_downstream") declare function ${lifecycle?.authority ? '__pulse_response_send_raw' : 'fastly_http_resp_send_downstream'}(handle: i32, body: i32, streaming: i32): i32
1185
1470
  @external("fastly_http_body", "new") declare function fastly_http_body_new(handleOut: usize): i32
1186
1471
  @external("fastly_http_body", "read") declare function fastly_http_body_read(handle: i32, buffer: usize, bufferLength: i32, readOut: usize): i32
1187
1472
  @external("fastly_http_body", "write") declare function fastly_http_body_write(handle: i32, buffer: usize, bufferLength: i32, end: i32, writtenOut: usize): i32
@@ -1195,8 +1480,11 @@ function __pulse_fastly_jwt_send_error(): void {}
1195
1480
  @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
1481
  @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
1482
  @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
1483
+ @external("fastly_kv_store", "delete") declare function fastly_kv_store_delete(store: i32, key: usize, keyLength: i32, configMask: i32, config: usize, pendingOut: usize): i32
1484
+ @external("fastly_kv_store", "delete_wait") declare function fastly_kv_store_delete_wait(handle: i32, kvErrorOut: usize): i32
1485
+ @external("fastly_kv_store", "list") declare function fastly_kv_store_list(store: i32, configMask: i32, config: usize, pendingOut: usize): i32
1486
+ @external("fastly_kv_store", "list_wait") declare function fastly_kv_store_list_wait(handle: i32, bodyOut: usize, kvErrorOut: usize): i32
1487
+ ${nativeLoggingSource(bindings.logEndpoint)}
1200
1488
  @external("wasi_snapshot_preview1", "clock_time_get") declare function wasi_snapshot_preview1_clock_time_get(clockId: i32, precision: i64, timeOut: usize): i32
1201
1489
 
1202
1490
  const PULSE_FASTLY_EFFECTS_ABI_VERSION: i64 = ${FASTLY_NATIVE_PLATFORM_CAPABILITIES_ABI_VERSION}
@@ -1233,20 +1521,43 @@ const PULSE_ERROR_SCHEMA: i32 = 1005
1233
1521
  const PULSE_ERROR_TRANSPORT: i32 = 1006
1234
1522
  const PULSE_ERROR_STATE: i32 = 1007
1235
1523
  const PULSE_ERROR_JWT: i32 = 1008
1524
+ const PULSE_ERROR_REQUEST_BODY: i32 = 1009
1236
1525
 
1237
1526
  class __PulseFastlyValue {
1527
+ ${tracksJsonDuplicates(plan) ? 'duplicateJsonKeys: bool = false' : ''}
1238
1528
  kind: i32 = PULSE_VALUE_UNDEFINED
1239
1529
  boolean: i32 = 0
1240
1530
  number: f64 = 0.0
1241
- text: string = ""
1242
- keys: Array<string> = new Array<string>()
1243
- values: Array<i32> = new Array<i32>()
1531
+ ${nativeStringFields}
1532
+ // Scalar handles do not need collection backing stores.
1533
+ private keysStorage: Array<string> | null = null
1534
+ get keys(): Array<string> {
1535
+ if (this.keysStorage === null) this.keysStorage = new Array<string>()
1536
+ return this.keysStorage!
1537
+ }
1538
+ // Scalar handles do not need collection backing stores.
1539
+ private valuesStorage: Array<i32> | null = null
1540
+ get values(): Array<i32> {
1541
+ if (this.valuesStorage === null) this.valuesStorage = new Array<i32>()
1542
+ return this.valuesStorage!
1543
+ }
1244
1544
  responseHandle: i32 = 0
1245
1545
  bodyHandle: i32 = 0
1546
+ ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? ' fetchHeaders: Array<__FCA_Header> | null = null;\n fetchJsonType: i32 = 0;' : ''}
1246
1547
  bodyLoaded: i32 = 0
1548
+ s3Body: bool = false
1549
+ s3Deadline: i64 = 0
1550
+ s3Length: i32 = 0
1247
1551
  status: i32 = 200
1248
- headers: Array<__PulseFastlyHeader> = new Array<__PulseFastlyHeader>()
1552
+ // Scalar handles do not need collection backing stores.
1553
+ private headersStorage: Array<__PulseFastlyHeader> | null = null
1554
+ get headers(): Array<__PulseFastlyHeader> {
1555
+ if (this.headersStorage === null) this.headersStorage = new Array<__PulseFastlyHeader>()
1556
+ return this.headersStorage!
1557
+ }
1558
+ binaryBody: Uint8Array | null = null
1249
1559
  immutable: bool = false
1560
+ ${nativeHandleOwnership.fields(options.handleTracing)}
1250
1561
  }
1251
1562
  class __PulseFastlyHeader {
1252
1563
  constructor(public name: string, public value: string) {}
@@ -1255,11 +1566,14 @@ class __PulseJsonParser {
1255
1566
  source: string
1256
1567
  index: i32 = 0
1257
1568
  failed: bool = false
1258
- constructor(source: string) { this.source = source }
1569
+ tooLarge: bool = false
1570
+ entries: i32 = 0
1571
+ constructor(source: string, public conditionalKv: bool = false) { this.source = source }
1259
1572
  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
1573
  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
1574
  value(depth: i32): i32 {
1262
- this.skip(); if (this.index >= this.source.length || depth > 64) { this.failed = true; return 0 }
1575
+ 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 }
1576
+ 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
1577
  const c = this.source.charCodeAt(this.index)
1264
1578
  if (c == 34) return __pulse_fastly_string_value(this.string())
1265
1579
  if (c == 123) return this.object(depth + 1)
@@ -1271,37 +1585,47 @@ class __PulseJsonParser {
1271
1585
  this.failed = true; return 0
1272
1586
  }
1273
1587
  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
1588
+ // Find a bounded span, then decode each UTF-16 unit once. Concatenating one
1589
+ // character at a time exhausts stub/fixed memory on a valid maximum KV value.
1590
+ const start = this.index + 1; let end = start; let plain = true;
1591
+ while (end < this.source.length) {
1592
+ const c = this.source.charCodeAt(end);
1593
+ if (c == 34) break;
1594
+ if (c == 92) { plain = false; end += 1; }
1595
+ else if (c < 32) plain = false;
1596
+ end += 1;
1597
+ }
1598
+ if (end >= this.source.length) { this.failed = true; return "" }
1599
+ // A validated span needs one owned string, without a temporary UTF-16 array.
1600
+ // Keep escaped and malformed spans on the existing decoder path.
1601
+ if (plain) { this.index = end + 1; return this.source.substring(start, end); }
1602
+ const output = new Uint16Array(end - start); let count = 0; this.index = start;
1603
+ while (this.index < end) {
1604
+ let c = this.source.charCodeAt(this.index++);
1278
1605
  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"
1606
+ if (this.index >= end) { this.failed = true; return "" }
1607
+ c = this.source.charCodeAt(this.index++);
1608
+ if (c == 34 || c == 92 || c == 47) {}
1609
+ else if (c == 98) c = 8;
1610
+ else if (c == 102) c = 12;
1611
+ else if (c == 110) c = 10;
1612
+ else if (c == 114) c = 13;
1613
+ else if (c == 116) c = 9;
1287
1614
  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
1615
+ if (this.index + 4 > end) { this.failed = true; return "" }
1616
+ c = 0;
1617
+ for (let i = 0; i < 4; i++) {
1618
+ const d = this.source.charCodeAt(this.index++);
1619
+ const digit = d >= 48 && d <= 57 ? d - 48 : d >= 65 && d <= 70 ? d - 55 : d >= 97 && d <= 102 ? d - 87 : -1;
1296
1620
  if (digit < 0) { this.failed = true; return "" }
1297
- code = (code << 4) | digit
1621
+ c = (c << 4) | digit;
1298
1622
  }
1299
- this.index += 4; output += String.fromCharCode(code)
1300
1623
  } else { this.failed = true; return "" }
1301
1624
  } else if (c < 32) { this.failed = true; return "" }
1302
- else output += String.fromCharCode(c)
1625
+ output[count++] = u16(c);
1303
1626
  }
1304
- this.failed = true; return ""
1627
+ this.index = end + 1;
1628
+ return String.UTF16.decodeUnsafe(output.dataStart, count * 2);
1305
1629
  }
1306
1630
  numberValue(): i32 {
1307
1631
  const start = this.index
@@ -1350,7 +1674,9 @@ class __PulseJsonParser {
1350
1674
  while (!this.failed) {
1351
1675
  this.skip(); if (this.index >= this.source.length || this.source.charCodeAt(this.index) != 34) { this.failed = true; break }
1352
1676
  const key = this.string(); this.skip()
1677
+ if (this.conditionalKv && __pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) { this.failed = true; break }
1353
1678
  if (this.index >= this.source.length || this.source.charCodeAt(this.index) != 58) { this.failed = true; break }
1679
+ ${tracksJsonDuplicates(plan) ? 'if (__pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) __pulse_fastly_value(output).duplicateJsonKeys = true' : ''}
1354
1680
  this.index += 1; host_value_object_set(output, __pulse_fastly_string_value(key), this.value(depth)); this.skip()
1355
1681
  if (this.index >= this.source.length) { this.failed = true; break }
1356
1682
  const c = this.source.charCodeAt(this.index); this.index += 1
@@ -1364,13 +1690,20 @@ class __PulseJsonParser {
1364
1690
  const PULSE_FASTLY_PENDING_NONE: i32 = 0
1365
1691
  const PULSE_FASTLY_PENDING_ASYNC: i32 = 1
1366
1692
  const PULSE_FASTLY_PENDING_READY: i32 = 2
1367
- @lazy const __pulse_fastly_values = new Array<__PulseFastlyValue>()
1693
+ ${nativeHandleOwnership.tableSource(options.handleTracing)}
1694
+ // Primitive handles have value semantics. Bounded caches keep admitted scalar
1695
+ // validation loops from retaining a fresh boxed value for every iteration.
1696
+ @lazy const __pulse_fastly_scalar_handles = new StaticArray<i32>(4)
1697
+ @lazy const __pulse_fastly_integer_handles = new StaticArray<i32>(65536)
1698
+ @lazy const __pulse_fastly_character_handles = new StaticArray<i32>(65537)
1699
+ @lazy const __pulse_fastly_literal_handles = new Map<string, i32>()
1368
1700
  @lazy const __pulse_fastly_pending = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1369
1701
  @lazy const __pulse_fastly_pending_mode = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1370
1702
  @lazy const __pulse_fastly_payloads = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1371
1703
  @lazy const __pulse_fastly_ready = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1372
1704
  @lazy const __pulse_fastly_grip_channels = new Array<string>()
1373
1705
  @lazy const __pulse_fastly_redactions = new Array<string>()
1706
+ @lazy const __pulse_fastly_field_redactions = new Array<string>()
1374
1707
  let __pulse_fastly_request_handle: i32 = 0
1375
1708
  let __pulse_fastly_request_body_handle: i32 = 0
1376
1709
  let __pulse_fastly_request_method: string = ""
@@ -1381,7 +1714,6 @@ let __pulse_fastly_request_body_loaded: i32 = 0
1381
1714
  let __pulse_fastly_last_error: i32 = 0
1382
1715
  let __pulse_fastly_error_stage: i32 = 0
1383
1716
  let __pulse_fastly_error_effect: i32 = -1
1384
- let __pulse_fastly_log_handle: i32 = -1
1385
1717
  let __pulse_fastly_jwt_error: i32 = 0
1386
1718
  let __pulse_fastly_jwt_secret_calls: i32 = 0
1387
1719
  let __pulse_fastly_jwt_crypto_calls: i32 = 0
@@ -1398,57 +1730,168 @@ function __pulse_fastly_out_i32(): StaticArray<i32> { return new StaticArray<i32
1398
1730
  function __pulse_fastly_out_value(out: StaticArray<i32>): i32 { return load<i32>(changetype<usize>(out)) }
1399
1731
  function __pulse_fastly_utf8(value: string): ArrayBuffer { return String.UTF8.encode(value, false) }
1400
1732
  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]) }
1733
+ ${nativeStringAssembly}
1734
+ ${nativeStringConcat}
1735
+ ${nativeStringTrim}
1736
+ ${nativeStringIndex}
1403
1737
  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) }
1738
+ function __pulse_fastly_string_value(value: string): i32 {
1739
+ const index = value.length == 0 ? 65536 : value.length == 1 ? value.charCodeAt(0) : -1;
1740
+ if (index >= 0) { const cached = unchecked(__pulse_fastly_character_handles[index]); if (cached != 0) return cached }
1741
+ const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_STRING; item.text = value;
1742
+ const handle = __pulse_fastly_put(item);
1743
+ if (index >= 0) { unchecked(__pulse_fastly_character_handles[index] = handle); ${options.handleTracing ? 'if (handle != 0) __pulse_handle_cache_roots.push(handle);' : ''} }
1744
+ return handle
1745
+ }
1405
1746
  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
1747
  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
1748
  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 }
1749
+ 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
1750
  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)) }
1751
+ // Walk complete JSON string tokens, including escaped quotes. Substring matching
1752
+ // a quoted field would still corrupt an unrelated token such as "prefix\\\"id".
1753
+ function __pulse_fastly_redact_fields(input: string): string {
1754
+ if (__pulse_fastly_field_redactions.length == 0) return input;
1755
+ let output = "", start = 0, cursor = 0;
1756
+ while (cursor < input.length) {
1757
+ if (input.charCodeAt(cursor) != 34) { cursor += 1; continue; }
1758
+ const begin = cursor++;
1759
+ while (cursor < input.length) {
1760
+ const code = input.charCodeAt(cursor++);
1761
+ if (code == 92 && cursor < input.length) cursor += 1;
1762
+ else if (code == 34) break;
1763
+ }
1764
+ const token = input.substring(begin, cursor);
1765
+ for (let i = 0; i < __pulse_fastly_field_redactions.length; i += 1) {
1766
+ if (token != unchecked(__pulse_fastly_field_redactions[i])) continue;
1767
+ output += input.substring(start, begin) + __pulse_fastly_quote("<redacted>");
1768
+ start = cursor; break;
1769
+ }
1770
+ }
1771
+ return output + input.substring(start);
1772
+ }
1773
+ 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 }
1774
+ 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
1775
  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
1776
  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" }
1777
+ function __pulse_fastly_is_unpaired(value: string, i: i32): bool {
1778
+ const c = value.charCodeAt(i);
1779
+ if (c >= 0xd800 && c <= 0xdbff) return i + 1 >= value.length || value.charCodeAt(i + 1) < 0xdc00 || value.charCodeAt(i + 1) > 0xdfff;
1780
+ if (c >= 0xdc00 && c <= 0xdfff) return i == 0 || value.charCodeAt(i - 1) < 0xd800 || value.charCodeAt(i - 1) > 0xdbff;
1781
+ return false;
1782
+ }
1783
+ function __pulse_fastly_quote(value: string): string {
1784
+ // Count once and write UTF-16 units once. Repeated concatenation can allocate
1785
+ // gigabytes for a valid bounded string when every byte needs JSON escaping.
1786
+ let size = 2;
1787
+ for (let i = 0; i < value.length; i++) {
1788
+ const c = value.charCodeAt(i);
1789
+ 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;
1790
+ }
1791
+ const out = new Uint16Array(size); let cursor = 0; out[cursor++] = 34;
1792
+ for (let i = 0; i < value.length; i++) {
1793
+ const c = value.charCodeAt(i);
1794
+ if (c == 34 || c == 92) { out[cursor++] = 92; out[cursor++] = u16(c); }
1795
+ else if (c == 8 || c == 9 || c == 10 || c == 12 || c == 13) {
1796
+ out[cursor++] = 92; out[cursor++] = c == 8 ? 98 : c == 9 ? 116 : c == 10 ? 110 : c == 12 ? 102 : 114;
1797
+ } else if (c < 32 || __pulse_fastly_is_unpaired(value, i)) {
1798
+ out[cursor++] = 92; out[cursor++] = 117;
1799
+ out[cursor++] = u16('0123456789abcdef'.charCodeAt((c >> 12) & 15)); out[cursor++] = u16('0123456789abcdef'.charCodeAt((c >> 8) & 15));
1800
+ out[cursor++] = u16('0123456789abcdef'.charCodeAt((c >> 4) & 15)); out[cursor++] = u16('0123456789abcdef'.charCodeAt(c & 15));
1801
+ } else out[cursor++] = u16(c);
1802
+ }
1803
+ out[cursor] = 34;
1804
+ return String.UTF16.decodeUnsafe(out.dataStart, out.byteLength);
1805
+ }
1806
+ function __pulse_fastly_json_parts(handle: i32, depth: i32, parts: Array<string>): void {
1807
+ 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
1808
  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"
1809
+ if (value.kind == PULSE_VALUE_BOOLEAN) { parts.push(value.boolean != 0 ? "true" : "false"); return }
1810
+ if (value.kind == PULSE_VALUE_NUMBER) { parts.push(__pulse_fastly_number_string(value.number)); return }
1811
+ if (value.kind == PULSE_VALUE_STRING) { parts.push(__pulse_fastly_quote(value.text)); return }
1812
+ 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 }
1813
+ 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 }
1814
+ parts.push("null")
1815
+ }
1816
+ function __pulse_fastly_json(handle: i32, depth: i32): string {
1817
+ // Join once, avoiding a complete copy of a multi-MiB leaf at every ancestor
1818
+ // and at every following member in the response or effect result.
1819
+ const parts = new Array<string>(); __pulse_fastly_json_parts(handle, depth, parts);
1820
+ return __pulse_fastly_join_strings(parts)
1425
1821
  }
1426
1822
  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
1823
  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
- 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)) }
1824
+ 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 "" } ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? 'if (__pulse_fastly_out_value(written)<0 || __pulse_fastly_out_value(written)>buffer.byteLength) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL,10+kind,-1);return ""; }' : ''} return __pulse_fastly_decode(buffer, __pulse_fastly_out_value(written)) }
1825
+ function __pulse_fastly_read_body(handle: i32, effectIndex: i32, requestText: bool = false): string {
1826
+ // One retained input allocation, including a byte to distinguish exact-limit
1827
+ // EOF from overflow. Short reads never allocate another chunk or prefix.
1828
+ if (__PULSE_SCHEMA_MAX_BYTES <= 0 || __PULSE_SCHEMA_MAX_BYTES > 1073741823) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, effectIndex); return "" }
1829
+ const bytes = new Uint8Array(__PULSE_SCHEMA_MAX_BYTES + 1), read = __pulse_fastly_out_i32();
1830
+ let total = 0;
1831
+ while (true) {
1832
+ if (!__pulse_fastly_body_read_ready(handle, effectIndex)) return "";
1833
+ const capacity = min<i32>(PULSE_FASTLY_BUFFER_BYTES, bytes.length - total);
1834
+ const status = fastly_http_body_read(handle, bytes.dataStart + total, capacity, changetype<usize>(read));
1835
+ if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" }
1836
+ const count = __pulse_fastly_out_value(read);
1837
+ if (count < 0 || count > capacity) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" }
1838
+ if (!count) break;
1839
+ total += count;
1840
+ if (total > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(requestText ? PULSE_ERROR_REQUEST_BODY : PULSE_ERROR_SCHEMA, 21, effectIndex); return "" }
1841
+ }
1842
+ // UTF-8 scalars may straddle host read chunks. Decode the bounded body once.
1843
+ if (requestText && !__pulse_request_utf8_valid(bytes, total)) { __pulse_fastly_fail(PULSE_ERROR_REQUEST_BODY, 24, effectIndex); return "" }
1844
+ return __pulse_fastly_decode(bytes, total);
1845
+ }
1846
+ function __pulse_fastly_body_read_ready(handle: i32, effectIndex: i32): bool { return true }
1847
+ function __pulse_fastly_request_header_text(name: string): string { const value = __pulse_header_lookup(__pulse_fastly_request_handle, name, false); return value === null ? "" : value }
1848
+ function __pulse_fastly_fetch_header_text(responseHandle: i32, name: string): string { const value = __pulse_header_lookup(responseHandle, name, true); return value === null ? "" : value }
1432
1849
  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
1850
 
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)) }
1851
+ function __pulse_fastly_scalar(kind: i32, boolean: i32, slot: i32): i32 {
1852
+ const cached = unchecked(__pulse_fastly_scalar_handles[slot]); if (cached != 0) return cached;
1853
+ const item = new __PulseFastlyValue(); item.kind = kind; item.boolean = boolean;
1854
+ 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
1855
+ }
1856
+ function host_value_undefined(): i32 { return __pulse_fastly_scalar(PULSE_VALUE_UNDEFINED, 0, 0) }
1857
+ function host_value_null(): i32 { return __pulse_fastly_scalar(PULSE_VALUE_NULL, 0, 1) }
1858
+ function host_value_boolean(value: i32): i32 { return __pulse_fastly_scalar(PULSE_VALUE_BOOLEAN, value != 0 ? 1 : 0, value != 0 ? 3 : 2) }
1859
+ ${plan.callables?.length || plan.binaryResources || plan.numericWorkspaces?.length ? require('./native-binary-resources.js').source() : ''}
1860
+ function host_value_number(value: f64): i32 {
1861
+ // Preserve negative zero, NaN, infinities and nonintegral values exactly.
1862
+ const cacheable = value >= 0 && value < 65536 && Math.floor(value) == value && (value != 0 || 1.0 / value > 0);
1863
+ const index = cacheable ? i32(value) : -1;
1864
+ if (index >= 0) { const cached = unchecked(__pulse_fastly_integer_handles[index]); if (cached != 0) return cached }
1865
+ const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NUMBER; item.number = value;
1866
+ 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
1867
+ }
1868
+ function host_value_string(pointer: i32, length: i32): i32 {
1869
+ const text = changetype<string>(pointer);
1870
+ if (text.length > 1 && text.length <= 128 && __pulse_fastly_literal_handles.has(text)) return __pulse_fastly_literal_handles.get(text);
1871
+ const handle = __pulse_fastly_string_value(text);
1872
+ 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);' : ''} }
1873
+ return handle
1874
+ }
1875
+ function host_effect_collection_count(handle: i32, max: i32, stateIndex: i32): i32 {
1876
+ const input = __pulse_fastly_value(handle)
1877
+ 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 }
1878
+ if (input.values.length > max) { __pulse_fastly_fail(PULSE_ERROR_STATE, 172, -1); return -1 }
1879
+ if (__pulse_invocation_count + input.values.length > PULSE_FASTLY_MAX_EFFECT_INVOCATIONS) { __pulse_fastly_fail(PULSE_ERROR_STATE, 170, -1); return -1 }
1880
+ return input.values.length
1881
+ }
1439
1882
  function host_value_array(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_ARRAY; return __pulse_fastly_put(item) }
1440
1883
  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
1884
  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
1885
  function host_value_object(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_OBJECT; return __pulse_fastly_put(item) }
1443
1886
  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
1887
  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() }
1888
+ 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") { ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? 'return __fca_status(item);' : ''} 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
1889
  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) }
1890
+ 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
1891
  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 }
1892
+ 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
1893
  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 }
1894
+ 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
1895
  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
1896
 
1454
1897
  function host_request_method(): i32 { return __pulse_fastly_string_value(__pulse_fastly_request_method) }
@@ -1459,13 +1902,36 @@ function __pulse_router_param_text(path: string, pattern: string, name: string):
1459
1902
  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
1903
  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
1904
  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 }
1905
+ function host_request_headers(): i32 { return __pulse_request_headers_read() }
1906
+ 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) }
1907
+ 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) }
1908
+ 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
1909
  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
1910
  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
1911
  function host_response_json(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, true) }
1912
+ function host_schema_decode(text: i32, schema: i32): i32 {
1913
+ const id = __pulse_fastly_value(schema)
1914
+ if (id.kind != PULSE_VALUE_STRING || id.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return 0 }
1915
+ const input = __pulse_fastly_value(text)
1916
+ if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }
1917
+ if (String.UTF8.byteLength(input.text) > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, -1); return 0 }
1918
+ const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(input.text, schema)' : '__pulse_fastly_parse_json(input.text)'}
1919
+ if (parsed <= 0) return 0
1920
+ const decoded = __pulse_fastly_schema_apply(id.text, parsed, false)
1921
+ if (decoded <= 0) return 0
1922
+ __pulse_fastly_deep_freeze(decoded)
1923
+ return decoded
1924
+ }
1925
+ function host_schema_encode(value: i32, schema: i32): i32 {
1926
+ const id = __pulse_fastly_value(schema)
1927
+ if (id.kind != PULSE_VALUE_STRING || id.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return 0 }
1928
+ ${textEncodeIds.length ? `const textResult = ${textEncodeIds.map(id => `id.text == ${quote(id)}`).join(' || ')}
1929
+ const projected = __pulse_fastly_schema_apply(id.text, value, true, textResult)` : 'const projected = __pulse_fastly_schema_apply(id.text, value, true)'}
1930
+ if (projected <= 0) return 0
1931
+ const text = ${textEncodeIds.length ? 'textResult ? __pulse_fastly_string(projected) : ' : ''}__pulse_fastly_json(projected, 0)
1932
+ if (String.UTF8.byteLength(text) > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, -1); return 0 }
1933
+ return ${textEncodeIds.length ? 'textResult ? projected : ' : ''}__pulse_fastly_string_value(text)
1934
+ }
1469
1935
  function host_response_text(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, false) }
1470
1936
  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
1937
  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 +1941,8 @@ function host_grip_subscribe(responseHandle: i32, optionsHandle: i32): i32 { con
1475
1941
  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
1942
  function host_kv_namespace(name: i32): i32 { return name }
1477
1943
  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) }
1944
+ 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) { ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? 'if (!__fca_json_type(response)) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA,44,-1); return 0; }' : `${options.fetchAccounting?.selection === 'canonical-single-get-json' ? 'if (!__fca_work()) return 0;' : ''} 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 }
1945
+ 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 = ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? '__fca_header(response, __pulse_fastly_string(nameHandle))' : '__pulse_header_lookup(response.responseHandle, __pulse_fastly_string(nameHandle), true)'}; return value === null ? host_value_undefined() : __pulse_fastly_string_value(value) }
1480
1946
  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
1947
  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
1948
  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 +1962,12 @@ function __pulse_fastly_jwt_record_verified(): void { __pulse_fastly_jwt_verifie
1496
1962
  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
1963
  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
1964
  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) }
1965
+ 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
1966
  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)) }
1967
+ 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 = ${options.fetchAccounting ? '__fca_new(0,' : '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 = ${options.fetchAccounting ? '__fca_new(1,' : '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); ${options.fetchAccounting ? 'if (!__fca_work()) return;' : ''} 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); ${options.fetchAccounting ? 'if (!__fca_work()) return;' : ''} 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]))); ${options.fetchAccounting ? 'if (!__fca_work()) return;' : ''} status = fastly_http_req_header_append(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 = ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? '__fca_write' : '__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 = ${options.fetchAccounting ? '__fca_dispatch' : 'fastly_http_req_send_async'}(requestHandle, bodyHandle, changetype<usize>(backendBytes), backendBytes.byteLength, changetype<usize>(pendingOut)); if (status != FASTLY_STATUS_OK) { ${options.fetchAccounting ? '__fca_cleanup();' : '__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) }
1968
+ function __pulse_fastly_fetch_begin(effectIndex: i32, payload: __PulseFastlyValue): void { ${options.fetchAccounting ? options.fetchAccounting.selection === 'canonical-fetch-cohorts' ? 'if (!__fca_start(effectIndex)) return;' : 'if (!__fca_start()) return;' : ''} 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])); headers = __pulse_fastly_fetch_headers(headers, effectIndex); if (__pulse_fastly_last_error != 0) return; 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
1969
  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) }
1970
+ ${assetBeginSource}
1505
1971
  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
1972
  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
1973
  function __pulse_fastly_grip_payload(payload: __PulseFastlyValue): __PulseFastlyValue { return __pulse_fastly_value(__pulse_fastly_payload_field(payload, "payload")) }
@@ -1510,27 +1976,32 @@ function __pulse_fastly_grip_hold_begin(effectIndex: i32, payload: __PulseFastly
1510
1976
  ${gripSecretSource}
1511
1977
  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
1978
  ${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) }
1979
+ 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
1980
  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
1981
  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
1982
  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
1983
  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) }
1984
+ ${assetResultSource}
1521
1985
  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
1986
  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) }
1987
+ ${lifecycle?.active ? `function __pulse_fastly_send_result(handle: i32): i32 {\n${lifecycle.site('response-send')}\n return __pulse_fastly_send_result_impl(handle)\n}\n` : ''}function ${lifecycle?.legacyName('__pulse_fastly_send_result') || '__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 ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? '__fca_transfer(value.responseHandle, value.bodyHandle)' : '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
1988
 
1525
1989
  ${jwtEvidenceSource}
1990
+ ${terminalErrorSource}
1526
1991
  ${es256KeyArtifactSource(es256KeyRecords)}
1527
1992
  ${jwtCryptoCompositionSource(jwtCrypto)}
1993
+ ${jwtSigningCryptoCompositionSource(jwtCrypto)}
1528
1994
  ${generateSchemaRuntime(plan)}
1529
1995
  ${effectResultSource(plan, bindings)}
1530
1996
  `;
1997
+ if (!hasAssets) return source;
1998
+ const writer = 'status = __pulse_fastly_write_body(bodyHandle, value.text);';
1999
+ if (!source.includes(writer)) throw new Error('Embedded asset response writer boundary drift.');
2000
+ return source.replace(writer, 'status = value.binaryBody !== null ? __pulse_fastly_write_binary(bodyHandle, value.binaryBody!) : __pulse_fastly_write_body(bodyHandle, value.text);');
1531
2001
  }
1532
2002
 
1533
2003
  function driverSource(plan, options = {}) {
2004
+ const lifecycle = options.lifecycle;
1534
2005
  const entryName = options.guestLinked === true
1535
2006
  ? 'pulse_fastly_handle'
1536
2007
  : '_start';
@@ -1544,33 +2015,41 @@ export function pulse_fastly_jwt_crypto_calls(): i32 { return __pulse_fastly_jwt
1544
2015
  export function pulse_fastly_jwt_clock_calls(): i32 { return __pulse_fastly_jwt_clock_calls }
1545
2016
  export function pulse_fastly_jwt_schema_calls(): i32 { return __pulse_fastly_jwt_schema_calls }
1546
2017
  export function ${entryName}(): void {
2018
+ if (__pulse_invocation_started) { __pulse_fastly_fail(PULSE_ERROR_STATE, 171, -1); return }
2019
+ __pulse_invocation_started = true
2020
+ __pulse_fastly_run_invocation()
2021
+ ${lifecycle?.site('invocation-end') ? lifecycle.site('invocation-end') + '\n' : ''} ${plan.effects.some(e => e.kind === 's3.getBody') ? '__s3_close_bodies()' : ''}
2022
+ __pulse_invocation_close()
2023
+ }
2024
+ function __pulse_fastly_run_invocation(): void {
1547
2025
  __pulse_fastly_last_error = fastly_abi_init(PULSE_FASTLY_EFFECTS_ABI_VERSION)
1548
2026
  if (__pulse_fastly_last_error != FASTLY_STATUS_OK) { __pulse_fastly_jwt_send_error(); return }
1549
2027
  const handles = new StaticArray<i32>(2)
1550
- const downstreamStatus = fastly_http_req_body_downstream_get(changetype<usize>(handles), changetype<usize>(handles) + 4)
2028
+ const downstreamStatus = ${options.resourceAccounting ? '__fra_incoming' : 'fastly_http_req_body_downstream_get'}(changetype<usize>(handles), changetype<usize>(handles) + 4)
1551
2029
  if (downstreamStatus != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 100, -1); __pulse_fastly_jwt_send_error(); return }
1552
2030
  __pulse_fastly_request_handle = load<i32>(changetype<usize>(handles))
1553
2031
  __pulse_fastly_request_body_handle = load<i32>(changetype<usize>(handles) + 4)
1554
- __pulse_fastly_request_method = __pulse_fastly_read_req_string(0)
2032
+ ${lifecycle?.site('request-acquired') ? lifecycle.site('request-acquired') + '\n' : ''} __pulse_fastly_request_method = __pulse_fastly_read_req_string(0)
1555
2033
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1556
2034
  __pulse_fastly_request_url = __pulse_fastly_read_req_string(1)
1557
2035
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1558
2036
  __pulse_fastly_request_path = __pulse_fastly_path(__pulse_fastly_request_url)
1559
2037
  let runStatus = pulse_start()
1560
- while (runStatus == 1 && __pulse_fastly_last_error == 0) {
2038
+ ${packageApplication.enabled(plan) ? packageApplication.driverLoop() : applicationErrors.settlementEnabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
1561
2039
  for (let effectIndex = 0; effectIndex < PULSE_FASTLY_EFFECT_COUNT; effectIndex += 1) {
1562
2040
  if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_NONE) continue
2041
+ const invocation = unchecked(__pulse_invocation_tickets[effectIndex])
1563
2042
  const result = __pulse_fastly_resolve_effect(effectIndex)
1564
2043
  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 }
2044
+ if (__pulse_invocation_settle(effectIndex, invocation, result) != 1) { __pulse_fastly_fail(PULSE_ERROR_STATE, 101, effectIndex); __pulse_fastly_jwt_send_error(); return }
1566
2045
  }
1567
2046
  runStatus = pulse_resume()
1568
- }
2047
+ }`}
1569
2048
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1570
2049
  if (runStatus != 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 102, -1); __pulse_fastly_jwt_send_error(); return }
1571
2050
  const result = pulse_result_handle()
1572
2051
  if (result <= 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 103, -1); __pulse_fastly_jwt_send_error(); return }
1573
- __pulse_fastly_jwt_attach_evidence(result)
2052
+ ${lifecycle?.site('result-claim') ? lifecycle.site('result-claim') + '\n' : ''} __pulse_fastly_jwt_attach_evidence(result)
1574
2053
  const sendStatus = __pulse_fastly_send_result(result)
1575
2054
  if (sendStatus != FASTLY_STATUS_OK) __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 104, -1)
1576
2055
  }
@@ -1578,9 +2057,10 @@ export function ${entryName}(): void {
1578
2057
  }
1579
2058
 
1580
2059
  function capabilityImports(effect) {
2060
+ if (effect.kind === 'time.now') return ['wasi_snapshot_preview1.clock_time_get'];
1581
2061
  if (effect.kind === 'config.get') return ['fastly_config_store.open', 'fastly_config_store.get'];
1582
2062
  if (effect.kind === 'secret.get') return ['fastly_secret_store.open', 'fastly_secret_store.get', 'fastly_secret_store.plaintext'];
1583
- if (effect.kind === 'jwt.verify') {
2063
+ if (['jwt.verify', 'jwt.sign'].includes(effect.kind)) {
1584
2064
  return [
1585
2065
  ...(effect.resource && effect.resource.keyType === 'secret'
1586
2066
  ? [
@@ -1592,6 +2072,7 @@ function capabilityImports(effect) {
1592
2072
  'wasi_snapshot_preview1.clock_time_get'
1593
2073
  ];
1594
2074
  }
2075
+ if (conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) return conditionalKv.kvImports(effect.kind).map(key => key.replace(':', '.'));
1595
2076
  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
2077
  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
2078
  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 +2080,7 @@ function capabilityImports(effect) {
1599
2080
  return [
1600
2081
  'fastly_http_req.new',
1601
2082
  'fastly_http_req.send_async',
1602
- 'fastly_http_req.pending_req_wait',
2083
+ 'fastly_http_req.pending_req_wait_v2',
1603
2084
  ...(effect.kind === 'grip.broadcast' ? ['fastly_secret_store.open', 'fastly_secret_store.get', 'fastly_secret_store.plaintext'] : [])
1604
2085
  ];
1605
2086
  }
@@ -1611,7 +2092,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1611
2092
  validatePlanBoundary(plan, options);
1612
2093
  const facts = packageFacts(plan, options);
1613
2094
  const jwtCrypto = selectedJwtCrypto(plan);
1614
- const es256KeyRecords = jwtCrypto && jwtCrypto.algorithm === 'ES256'
2095
+ const es256KeyRecords = jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
1615
2096
  ? resolveEs256KeyArtifacts(plan, facts)
1616
2097
  : Object.freeze([]);
1617
2098
  if (jwtCrypto && jwtCrypto.guestUnitRequired && facts.guestUnits.length !== 1) {
@@ -1634,25 +2115,89 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1634
2115
  }
1635
2116
  );
1636
2117
  }
2118
+ const packageDuration = plan.packages?.application?.requestLimits?.maxDurationMs;
2119
+ if (packageDuration !== undefined) options = { ...options, maxDurationMs: Math.min(options.maxDurationMs ?? packageDuration, packageDuration) };
1637
2120
  const bindings = resolveCapabilityBindings(plan, options);
1638
- const portable = generateCanonicalNativeAssemblyScript(plan, options);
2121
+ const selectedExtensions = hostExtensions.select(plan, options);
2122
+ const extensionPolicy = selectedExtensions.length ? hostExtensions.policy(selectedExtensions,
2123
+ (options.targetDescriptor?.finalWasmPolicy?.permittedImports || FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS.map(([module, name]) => ({ module, name, kind: 'function' })))) : undefined;
2124
+ const forwardingAccounting = require('./native-forwarding-accounting.js').profile(plan, bindings);
2125
+ const fetchAccounting = require('./native-fetch-accounting.js').profile(plan, bindings, FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES)
2126
+ || require('./native-fetch-cohorts.js').profile(plan, bindings, FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES);
2127
+ const resourceAccounting = forwardingAccounting || fetchAccounting;
2128
+ // Trusted provider-internal services, never a project/package hook API.
2129
+ const lifecycle = lifecycleBindings.create(plan, bindings, [...require('./native-fetch-accounting.js').hooks(fetchAccounting), ...hostExtensions.hooks(selectedExtensions), ...(options.lifecycleServices || [])]);
2130
+ // Keep final provider eligibility authoritative over the portable artifact
2131
+ // and caller options. Only the selected crypto guest uses the linked policy;
2132
+ // that policy excludes active codecs regardless of JSON arena configuration.
2133
+ const numericRegions = facts.guestUnits.length === 0 ? 'fastly-numeric-regions-v2'
2134
+ : jwtCrypto && jwtCrypto.guestUnitRequired ? 'fastly-linked-numeric-regions-v1' : undefined;
2135
+ const portable = packageApplication.enabled(plan) ? packageApplication.portable(plan) : generateCanonicalNativeAssemblyScript(plan, {
2136
+ ...options, numericRegions, lifecycle,
2137
+ handleOwnership: facts.guestUnits.length === 0 ? 'fastly-handle-tracing-v1' : undefined
2138
+ });
2139
+ nativeHandleOwnership.validateRoots(plan, portable.manifest);
1639
2140
  const portableSource = stripPulseHostImports(portable.source);
1640
2141
  const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
1641
- const jwtSource = hasJwt ? pulseJwtAssemblyScriptSource() : undefined;
1642
- const source = [
2142
+ const hasSign = plan.effects.some(effect => effect.kind === 'jwt.sign');
2143
+ const jwtSource = hasJwt || hasSign ? pulseJwtAssemblyScriptSource({ includeSigning: hasSign }) : undefined;
2144
+ let source = [
1643
2145
  '/* Generated by Pulse Pass98 Fastly native platform capability compiler. */',
1644
2146
  'export function __pulse_fastly_abort(message: string | null, fileName: string | null = null, line: u32 = 0, column: u32 = 0): void { unreachable() }',
1645
2147
  '',
1646
2148
  fastlyRuntimeSource(plan, bindings, {
1647
- ...options,
2149
+ ...options, lifecycle, fetchAccounting,
2150
+ handleTracing: portable.manifest.handleOwnership?.enabled === true,
1648
2151
  jwtCrypto,
1649
2152
  es256KeyRecords
1650
2153
  }),
1651
2154
  jwtSource ? jwtSource.source : '',
2155
+ ...(selectedExtensions.length ? [hostExtensions.source(selectedExtensions)] : []),
2156
+ ((plan.effects || []).some(effect => ['fetch', 'grip.publish', 'grip.broadcast'].includes(effect.kind)) || bindings.s3.length > 0)
2157
+ ? fs.readFileSync(path.join(__dirname, 'native-send-error-detail.as.ts'), 'utf8') : '',
2158
+ require('./s3-native.js').s3NativeSource(plan, bindings),
2159
+ 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') : '',
2160
+ require('./time-native.js').timeNativeSource(plan),
2161
+ require('./digest-native.js').digestNativeSource(plan),
2162
+ conditionalKv.kvNativeSource(plan),
2163
+ require('./native-request-body.js').runtimeSource(),
2164
+ require('./native-request-headers.js').runtimeSource(),
2165
+ require('./native-header-lookup.js').runtimeSource(),
2166
+ fs.readFileSync(path.join(__dirname, 'native-transport-details.as.ts'), 'utf8'),
2167
+ require('./native-fetch-errors.js').runtimeSource(dynamicBackends.active(bindings)),
2168
+ require('./native-fetch-deadlines.js').runtimeSource(plan, fetchAccounting),
2169
+ applicationErrors.settlementEnabled(plan) ? applicationErrors.runtimeSource(plan) : '',
2170
+ effectInvocations.runtimeSource({ packageCompletion: packageApplication.enabled(plan), lifecycle }),
2171
+ ...(portable.manifest.numericRegions?.accounting === 'fastly-read-loop-v1' && portable.manifest.numericRegions.regions.length > 0
2172
+ ? [nativeValueBudget.numericRegionSource()] : []),
1652
2173
  portableSource,
1653
- driverSource(plan, { guestLinked: facts.guestUnits.length > 0 }),
2174
+ nativeHandleOwnership.providerSource(plan, portable.manifest.handleOwnership?.enabled === true),
2175
+ driverSource(plan, { guestLinked: facts.guestUnits.length > 0, lifecycle, resourceAccounting: forwardingAccounting }),
2176
+ ...(forwardingAccounting ? [require('./native-forwarding-accounting.js').source(forwardingAccounting)] : []),
2177
+ ...(fetchAccounting ? [require(fetchAccounting.selection === 'canonical-fetch-cohorts' ? './native-fetch-cohorts.js' : './native-fetch-accounting.js').source(fetchAccounting)] : []),
1654
2178
  ''
1655
2179
  ].join('\n');
2180
+ if (applicationErrors.settlementEnabled(plan)) source = applicationErrors.instrument(source);
2181
+ source = require('./native-request-headers.js').instrument(source, applicationErrors.enabled(plan), /\bhost_request_headers\(/.test(portableSource));
2182
+ source = require('./native-header-lookup.js').instrument(source, lifecycle);
2183
+ source = require('./native-fetch-errors.js').instrument(source, lifecycle);
2184
+ source = require('./native-fetch-deadlines.js').instrument(source, plan, fetchAccounting);
2185
+ source = require('./native-request-body.js').instrument(source, applicationErrors.enabled(plan), lifecycle);
2186
+ if (packageApplication.enabled(plan)) source = packageApplication.instrument(source);
2187
+ source = effectInvocations.instrument(source);
2188
+ source = require('./request-budget.js').instrumentRequestBudget(source, bindings.maxDurationMs, plan, lifecycle, fetchAccounting);
2189
+ source = packageApplication.terminalFailure(source, plan);
2190
+ source = nativeValueBudget.instrument(source, plan);
2191
+ source = require('./native-body-forwarding.js').instrument(source, plan, bindings);
2192
+ source = dynamicBackends.instrument(source, bindings, true, (options.bindings || options).backends);
2193
+ // Forwarding owns a specialized terminal fetch implementation. Admission
2194
+ // above rejects outcome/timing options; generic fetch anchors do not exist here.
2195
+ if (!require('./native-body-forwarding.js').enabled(plan)) {
2196
+ source = require('./native-fetch-outcomes.js').instrument(source, plan);
2197
+ source = require('./native-fetch-timing.js').instrument(source, plan);
2198
+ }
2199
+ lifecycle.assertSites();
2200
+ if (lifecycle.supportSource) source += '\n' + lifecycle.supportSource;
1656
2201
  const effectKinds = Object.freeze((plan.effects || []).reduce((output, effect) => {
1657
2202
  output[effect.kind] = (output[effect.kind] || 0) + 1;
1658
2203
  return output;
@@ -1664,9 +2209,10 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1664
2209
  capability: effect.capability,
1665
2210
  package: effect.package,
1666
2211
  contractId: effect.contractId,
1667
- imports: Object.freeze(capabilityImports(effect))
2212
+ imports: Object.freeze([...capabilityImports(effect), ...(bindings.backends.some(b => b.index === index && b.dynamic) ? ['fastly_http_req.register_dynamic_backend'] : [])])
1668
2213
  })));
1669
2214
  const manifest = Object.freeze({
2215
+ ...(resourceAccounting ? { resourceAccounting } : {}),
1670
2216
  version: FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION,
1671
2217
  generatorVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_GENERATOR_VERSION,
1672
2218
  abiVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_ABI_VERSION,
@@ -1676,10 +2222,23 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1676
2222
  portableGeneratorVersion: portable.version,
1677
2223
  portableSourceHash: portable.sourceHash,
1678
2224
  schemaCodecs: portable.manifest.schemaCodecs,
2225
+ ...(portable.manifest.numericRegions ? { numericRegions: portable.manifest.numericRegions } : {}),
2226
+ handleOwnership: portable.manifest.handleOwnership ? Object.freeze({ ...portable.manifest.handleOwnership,
2227
+ providerRoots: portable.manifest.handleOwnership.enabled ? Object.freeze(nativeHandleOwnership.providerRoots(plan)) : Object.freeze([])
2228
+ }) : Object.freeze({
2229
+ policy: null, enabled: false,
2230
+ reason: packageApplication.enabled(plan) ? 'package-owned-emitter' : 'linked-runtime',
2231
+ roots: Object.freeze([]), checkpoints: 'none'
2232
+ }),
2233
+ handlerBodies: portable.manifest.handlerBodies,
2234
+ ...(portable.manifest.reportOwnership ? { reportOwnership: portable.manifest.reportOwnership } : {}),
2235
+ dispatcher: portable.manifest.dispatcher,
2236
+ stages: portable.manifest.stages,
2237
+ helperBodies: portable.manifest.helperBodies,
1679
2238
  crypto: portable.manifest.crypto,
1680
2239
  jwt: jwtSource
1681
2240
  ? Object.freeze({
1682
- status: jwtCrypto && jwtCrypto.algorithm === 'ES256'
2241
+ status: jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
1683
2242
  ? 'implemented-g4'
1684
2243
  : 'implemented-e2',
1685
2244
  package: jwtSource.owner,
@@ -1698,13 +2257,19 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1698
2257
  })
1699
2258
  : Object.freeze({ status: 'inactive' }),
1700
2259
  effectCount: (plan.effects || []).length,
2260
+ effectInvocations: effectInvocations.policy,
2261
+ ...(lifecycle.active ? { lifecycle: lifecycle.plan } : {}),
2262
+ ...(selectedExtensions.length ? { hostExtensions: hostExtensions.evidence(selectedExtensions, lifecycle, extensionPolicy) } : {}),
1701
2263
  effectKinds,
1702
2264
  continuationCount: (plan.continuations || []).length,
1703
2265
  groupedContinuationCount: (plan.continuations || []).filter((entry) => (entry.effectIds || []).length > 1).length,
1704
2266
  bindings,
1705
2267
  backends: bindings.backends,
1706
2268
  capabilityDiagnostics,
1707
- allowedImports: FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS,
2269
+ ...(dynamicBackends.active(bindings) ? { dynamicBackends: dynamicBackends.describe((options.bindings || options).backends) } : {}),
2270
+ allowedImports: dynamicBackends.active(bindings)
2271
+ ? Object.freeze([...FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS, Object.freeze(['fastly_http_req', 'register_dynamic_backend'])])
2272
+ : FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS,
1708
2273
  requiredImports: requiredImportsForPlan(plan, bindings),
1709
2274
  requiredExports: Object.freeze([
1710
2275
  '_start',
@@ -1712,27 +2277,30 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1712
2277
  'pulse_fastly_error_stage',
1713
2278
  'pulse_fastly_error_effect',
1714
2279
  'pulse_plan_hash_ptr',
1715
- 'pulse_plan_hash_length'
2280
+ 'pulse_plan_hash_length',
2281
+ ...(bindings.maxDurationMs === undefined ? [] : ['pulse_fastly_request_expired'])
1716
2282
  ]),
1717
2283
  policy: Object.freeze({
2284
+ ...(dynamicBackends.active(bindings) ? { dynamicBackends: dynamicBackends.describe((options.bindings || options).backends) } : {}),
2285
+ ...(nativeValueBudget.hasBoundedReadLoop(plan) ? { readLoopMemory: nativeValueBudget.policy } : {}),
1718
2286
  nativeFastly: true,
1719
2287
  provider: 'fastly',
1720
2288
  providerNeutralInput: true,
1721
2289
  javascriptRuntime: false,
1722
2290
  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',
2291
+ wasi: requiredImportsForPlan(plan, bindings).filter(key => key.startsWith('wasi_snapshot_preview1:')).map(key => key.split(':')[1]).join(', ').replace(/(.+)/, '$1 only') || false,
2292
+ effects: FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.join(', '),
1725
2293
  continuations: true,
1726
2294
  groupedEffects: 'existing fetch start-before-wait ordering remains intact',
1727
2295
  config: 'Fastly Config Store open/get with undefined for missing values',
1728
2296
  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`
2297
+ jwt: hasJwt || hasSign
2298
+ ? `package-owned compact-JWS, registered-claim, and bounded issuance semantics composed with ${jwtCrypto.realization}; provider-owned request, secret, clock, schema, and transport`
1731
2299
  : 'inactive',
1732
- kv: 'Fastly KV Store async lookup/insert with Pulse JSON envelope compatibility',
2300
+ 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
2301
  assets: 'Fastly KV Store lookup with provider-owned content type, cache headers, and GET/HEAD body ownership',
1734
2302
  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',
2303
+ 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
2304
  schemas: 'fetch JSON decode and structured response encode',
1737
2305
  opaqueResponse: 'origin fetch response/body handles and direct GRIP hold responses pass through without JavaScript runtime ownership',
1738
2306
  transportErrors: 'host status failures remain distinct from HTTP response status',
@@ -1763,13 +2331,16 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1763
2331
  });
1764
2332
  }
1765
2333
 
1766
- function inspectFastlyNativePlatformCapabilitiesWasm(input) {
2334
+ function inspectFastlyNativePlatformCapabilitiesWasm(input, options = {}) {
1767
2335
  const bytes = Buffer.isBuffer(input) ? input : fs.readFileSync(path.resolve(input));
2336
+ assertFastlyWasmBudget(bytes.length, options.maxWasmBytes);
1768
2337
  if (!WebAssembly.validate(bytes)) fail('Generated Fastly native platform capabilities module is not valid WebAssembly.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_WASM_INVALID');
1769
2338
  const module = new WebAssembly.Module(bytes);
1770
2339
  const imports = WebAssembly.Module.imports(module).map((entry) => Object.freeze({ module: entry.module, name: entry.name, kind: entry.kind }));
1771
2340
  const exports = WebAssembly.Module.exports(module).map((entry) => Object.freeze({ name: entry.name, kind: entry.kind }));
1772
- const unexpected = imports.filter((entry) => !FASTLY_NATIVE_PLATFORM_CAPABILITIES_ALLOWED_IMPORTS.has(`${entry.module}:${entry.name}`));
2341
+ const permitted = options.extensionPolicy ? new Set(options.extensionPolicy.permittedImports.map(entry => `${entry.module}:${entry.name}:${entry.kind}`)) : null;
2342
+ if (options.extensionPolicy) require('@pulse-compute/wasm-contracts/provider/toolchain').hostExtensions.auditWasm(bytes, options.extensionPolicy);
2343
+ const unexpected = imports.filter((entry) => permitted ? !permitted.has(`${entry.module}:${entry.name}:${entry.kind}`) : entry.kind !== 'function' || !FASTLY_NATIVE_PLATFORM_CAPABILITIES_ALLOWED_IMPORTS.has(`${entry.module}:${entry.name}`));
1773
2344
  if (unexpected.length > 0) fail('Generated Fastly native platform capabilities module imports functions outside the Pass98 ABI.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORT_UNSUPPORTED', { unexpected, imports });
1774
2345
  const forbidden = imports.filter((entry) => /pulse_host|js[_-]?compute/i.test(`${entry.module}:${entry.name}`));
1775
2346
  if (forbidden.length > 0) fail('Generated Fastly native platform capabilities module imports a forbidden runtime.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_RUNTIME_FORBIDDEN', { forbidden });
@@ -1780,7 +2351,6 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
1780
2351
  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
2352
  const missingExports = requiredExports.filter((name) => !exportNames.has(name));
1782
2353
  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
2354
  return Object.freeze({
1785
2355
  valid: true,
1786
2356
  bytes: bytes.length,
@@ -1793,6 +2363,7 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
1793
2363
  }
1794
2364
 
1795
2365
  function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2366
+ const maxWasmBytes = normalizeFastlyMaxWasmBytes(options.maxWasmBytes);
1796
2367
  const generated = generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options);
1797
2368
  const cwd = path.resolve(options.cwd || process.cwd());
1798
2369
  const compilerFallback = path.resolve(__dirname, '..', '..', '..', '..', 'wasm', 'packages', 'compiler');
@@ -1825,6 +2396,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1825
2396
  }
1826
2397
  jsonAs = Object.freeze({ transform, dependencyRoot, version: packageJson.version });
1827
2398
  }
2399
+ if (options.emitWat !== undefined && typeof options.emitWat !== 'boolean') throw new TypeError('emitWat must be a boolean.');
2400
+ const emitWat = options.emitWat === true;
1828
2401
  const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-fastly-native-platform-'));
1829
2402
  const requestedOutDir = options.outDir ? path.resolve(options.outDir) : undefined;
1830
2403
  const outputDir = requestedOutDir || stagingDir;
@@ -1832,6 +2405,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1832
2405
  const sourceFile = path.join(stagingDir, 'fastly-native-platform-capabilities.as.ts');
1833
2406
  const wasmFile = path.join(outputDir, options.wasmFile || 'fastly-native-platform-capabilities.wasm');
1834
2407
  const watFile = path.join(outputDir, options.watFile || 'fastly-native-platform-capabilities.wat');
2408
+ if (!emitWat) fs.rmSync(watFile, { force: true });
1835
2409
  const guestLinked = generated.guestUnits.length > 0;
1836
2410
  // asc resolves custom runtimes like module names and appends ".ts".
1837
2411
  const fixedMemoryRuntimeModule = path.join(__dirname, 'fixed-memory-runtime.as');
@@ -1848,14 +2422,17 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1848
2422
  asc.script,
1849
2423
  path.basename(sourceFile),
1850
2424
  '--outFile', assemblyScriptWasmFile,
1851
- '--textFile', assemblyScriptWatFile,
1852
2425
  '--runtime', guestLinked
1853
2426
  ? fixedMemoryRuntimeModule
1854
2427
  : (schemaCodecsActive ? 'incremental' : 'stub'),
1855
2428
  '--noAssert',
1856
2429
  '--optimize'
1857
2430
  ];
1858
- const optimization = appendAssemblyScriptOptimizationArgs(args, nativeOptimization);
2431
+ if (emitWat) args.push('--textFile', assemblyScriptWatFile);
2432
+ const optimization = appendAssemblyScriptOptimizationArgs(args, nativeOptimization, { guestLinked });
2433
+ if (!guestLinked && (plan.callables?.length || nativeValueBudget.hasBoundedReadLoop(plan) || plan.effects.some(effect => effect.kind === 'crypto.digestText' || effect.kind.startsWith('s3.')))) {
2434
+ args.push('--maximumMemory', String(nativeValueBudget.policy.maximumMemoryPages));
2435
+ }
1859
2436
  if (generated.guestUnits.length > 0) {
1860
2437
  args.push(
1861
2438
  '--disable', 'bulk-memory',
@@ -1878,17 +2455,20 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1878
2455
  '--path', jsonAs.dependencyRoot
1879
2456
  );
1880
2457
  }
1881
- const startedAt = Date.now();
2458
+ require('@pulse-compute/wasm-build-support/native-optimization').appendJsonCacheArenaArgs(args, options.elideJsonCacheArena, schemaCodecsActive);
2459
+ const readReportCapture = options.reportCapture === false ? () => null : prepareReportCapture(args, stagingDir, generated.manifest,
2460
+ require(path.join(asc.packageRoot, 'package.json')).version, 'fastly-native-platform-capabilities.as/');
2461
+ const startedAt = process.hrtime.bigint();
1882
2462
  const result = spawnSync(asc.executable, args, {
1883
2463
  cwd: stagingDir,
1884
2464
  encoding: 'utf8',
1885
2465
  maxBuffer: 64 * 1024 * 1024,
1886
2466
  timeout: Number(options.timeoutMs || 180000),
1887
2467
  env: schemaCodecsActive
1888
- ? { ...process.env, JSON_STRICT: 'true', JSON_USE_FAST_PATH: '0' }
2468
+ ? { ...process.env, JSON_STRICT: 'true', JSON_USE_FAST_PATH: '0', JSON_MODE: 'NAIVE' }
1889
2469
  : process.env
1890
2470
  });
1891
- const assemblyScriptDurationMs = Date.now() - startedAt;
2471
+ const assemblyScriptDurationMs = Math.round(Number(process.hrtime.bigint() - startedAt) / 1e6);
1892
2472
  if (result.error || result.status !== 0 || !fs.existsSync(assemblyScriptWasmFile)) {
1893
2473
  fail('AssemblyScript failed to compile the Fastly native platform capabilities module.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_ASC_FAILED', {
1894
2474
  status: result.status,
@@ -1932,7 +2512,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1932
2512
  failureMessage: 'Fastly Native start wrapper composition failed.'
1933
2513
  }
1934
2514
  );
1935
- runGuestTool(
2515
+ if (emitWat) runGuestTool(
1936
2516
  'wasm-dis',
1937
2517
  [wasmFile, '--mvp-features', '-o', watFile],
1938
2518
  {
@@ -1962,7 +2542,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1962
2542
  });
1963
2543
  }
1964
2544
  const primaryWasm = fs.readFileSync(wasmFile);
1965
- const primaryWat = fs.existsSync(watFile) ? fs.readFileSync(watFile, 'utf8') : '';
2545
+ const primaryWat = emitWat ? fs.readFileSync(watFile, 'utf8') : '';
1966
2546
  const primaryArtifact = Object.freeze({
1967
2547
  bytes: primaryWasm.length,
1968
2548
  sha256: sha256(primaryWasm)
@@ -1995,7 +2575,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1995
2575
  realization = Object.freeze({
1996
2576
  ...realization,
1997
2577
  wasm: stageResult.wasm,
1998
- wat: stageResult.wat,
2578
+ // Required guest-link disassembly audits remain enabled.
2579
+ wat: emitWat ? stageResult.wat : '',
1999
2580
  guestUnits: stageResult.guestUnits,
2000
2581
  guestLink: Object.freeze({
2001
2582
  version: stageResult.version,
@@ -2013,23 +2594,28 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2013
2594
  const wat = realization.wat;
2014
2595
  if (requestedOutDir && generated.guestUnits.length > 0) {
2015
2596
  fs.writeFileSync(wasmFile, wasm);
2016
- fs.writeFileSync(watFile, wat, 'utf8');
2597
+ if (emitWat) fs.writeFileSync(watFile, wat, 'utf8');
2017
2598
  }
2018
- const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm);
2599
+ const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm, { maxWasmBytes, extensionPolicy: generated.manifest.hostExtensions?.importPolicy });
2019
2600
  const imported = new Set(inspection.imports.map((entry) => `${entry.module}:${entry.name}`));
2020
2601
  const requiredImports = requiredImportsForPlan(plan, generated.bindings);
2021
2602
  const missingRequiredImports = requiredImports.filter((key) => !imported.has(key));
2022
2603
  if (missingRequiredImports.length > 0) fail('Generated Fastly native platform capabilities module is missing plan-required host imports.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORT_MISSING', { missingRequiredImports, imports: inspection.imports, requiredImports });
2023
2604
  const manifest = Object.freeze({
2024
2605
  ...generated.manifest,
2606
+ ...(generated.manifest.hostExtensions ? { hostExtensions: Object.freeze({ ...generated.manifest.hostExtensions,
2607
+ finalWasmSha256: inspection.sha256,
2608
+ observedImports: require('@pulse-compute/wasm-contracts/provider/toolchain').hostExtensions.auditWasm(wasm, generated.manifest.hostExtensions.importPolicy).observedImports }) } : {}),
2025
2609
  compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
2026
2610
  assemblyScript: Object.freeze({ package: 'assemblyscript', version: require(path.join(asc.packageRoot, 'package.json')).version }),
2027
2611
  jsonAs: jsonAs
2028
- ? Object.freeze({ package: 'json-as', version: jsonAs.version, transform: true, strict: true })
2612
+ ? Object.freeze({ package: 'json-as', version: jsonAs.version, transform: true, strict: true, mode: 'NAIVE', fastPath: false, elideDisabledCacheArena: options.elideJsonCacheArena === true })
2029
2613
  : undefined,
2030
2614
  optimization,
2031
2615
  wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
2032
- wat: Object.freeze({ bytes: Buffer.byteLength(wat), sha256: sha256(wat) }),
2616
+ wat: Object.freeze(emitWat
2617
+ ? { emitted: true, bytes: Buffer.byteLength(wat), sha256: sha256(wat) }
2618
+ : { emitted: false, bytes: 0, sha256: null }),
2033
2619
  importModules: inspection.importModules,
2034
2620
  imports: inspection.imports,
2035
2621
  requiredImports,
@@ -2043,10 +2629,17 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2043
2629
  providerPackaging: realization.guestLink.providerPackaging
2044
2630
  } : {})
2045
2631
  });
2046
- const durationMs = Date.now() - startedAt;
2632
+ const durationMs = Math.round(Number(process.hrtime.bigint() - startedAt) / 1e6);
2047
2633
  return Object.freeze({
2048
2634
  version: FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION,
2049
2635
  compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
2636
+ reportAttribution: readReportCapture(wasm),
2637
+ buildObservation: require('@pulse-compute/wasm-build-support/native-optimization').observeAssemblyScript(args, {
2638
+ version: require(path.join(asc.packageRoot, 'package.json')).version,
2639
+ assemblyScriptRoot: asc.packageRoot, timeoutMs: Number(options.timeoutMs || 180000),
2640
+ compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
2641
+ durationMs: assemblyScriptDurationMs, jsonAsVersion: jsonAs && jsonAs.version
2642
+ }),
2050
2643
  plan,
2051
2644
  generated,
2052
2645
  source: generated.source,
@@ -2062,7 +2655,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2062
2655
  assemblyScriptDurationMs,
2063
2656
  primaryArtifact,
2064
2657
  startWrapper,
2065
- output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile }) : undefined
2658
+ output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile: emitWat ? watFile : null }) : undefined
2066
2659
  });
2067
2660
  } finally {
2068
2661
  fs.rmSync(stagingDir, { recursive: true, force: true });
@@ -2080,10 +2673,12 @@ function writeFastlyNativePlatformCapabilitiesModule(compiled, outDir, options =
2080
2673
  const manifestFile = path.join(target, options.manifestFile || 'fastly-native-platform-capabilities-manifest.json');
2081
2674
  fs.writeFileSync(sourceFile, compiled.source, 'utf8');
2082
2675
  fs.writeFileSync(wasmFile, compiled.wasm);
2083
- fs.writeFileSync(watFile, compiled.wat, 'utf8');
2676
+ const emitWat = compiled.manifest.wat.emitted !== false;
2677
+ if (emitWat) fs.writeFileSync(watFile, compiled.wat, 'utf8');
2678
+ else fs.rmSync(watFile, { force: true });
2084
2679
  fs.writeFileSync(planFile, `${stableStringify(compiled.plan, 2)}\n`, 'utf8');
2085
2680
  fs.writeFileSync(manifestFile, `${stableStringify(compiled.manifest, 2)}\n`, 'utf8');
2086
- return Object.freeze({ target, sourceFile, wasmFile, watFile, planFile, manifestFile, manifest: compiled.manifest });
2681
+ return Object.freeze({ target, sourceFile, wasmFile, watFile: emitWat ? watFile : null, planFile, manifestFile, manifest: compiled.manifest });
2087
2682
  }
2088
2683
 
2089
2684
  module.exports = Object.freeze({