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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +6 -6
  2. package/dist/config-schema.json +7 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +2 -0
  6. package/dist/index.js.map +1 -1
  7. package/package.json +11 -11
  8. package/src/build/canonical-target.js +10 -2
  9. package/src/build/effect-invocations.js +49 -0
  10. package/src/build/embedded-assets.as.ts +32 -0
  11. package/src/build/kv-native.as.ts +18 -8
  12. package/src/build/native-application-errors.js +49 -26
  13. package/src/build/native-http-effects.js +41 -12
  14. package/src/build/native-platform-capabilities.js +346 -120
  15. package/src/build/native-string-values.js +1 -1
  16. package/src/build/native-value-budget.js +58 -0
  17. package/src/build/package-native-application.js +94 -0
  18. package/src/build/request-budget.js +3 -1
  19. package/src/build/s3-body.as.ts +97 -0
  20. package/src/build/s3-native.as.ts +9 -3
  21. package/src/build/s3-native.js +2 -2
  22. package/src/build/schema-admission.js +83 -0
  23. package/src/build/schema-nested-json.js +93 -0
  24. package/src/build/schema-scalar-record.js +41 -0
  25. package/src/build/wasm-budget.js +28 -0
  26. package/src/config-api.js +2 -0
  27. package/src/config-schema.json +7 -0
  28. package/src/index.ts +5 -0
  29. package/src/javascript/jwt-verifier.js +14 -4
  30. package/src/javascript/package-effects.js +1 -1
  31. package/src/javascript/target-support-policy.js +11 -5
  32. package/src/javascript/target.js +10 -0
  33. package/src/provider-contract.js +4 -2
  34. package/src/testing/native-http-effects-host.js +5 -1
  35. package/src/testing/native-platform-capabilities-host.js +24 -3
  36. package/src/toolchain/config.js +4 -0
  37. package/src/toolchain/index.js +30 -1
  38. package/src/toolchain/native-execution.js +1 -0
  39. package/src/toolchain/s3.js +2 -2
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ const { normalizeFastlyMaxWasmBytes } = require('../src/build/wasm-budget.js') as { normalizeFastlyMaxWasmBytes(value?: number): number };
1
2
  const { normalizeRequestDuration } = require('@pulse-compute/runtime/host') as { normalizeRequestDuration(value?: number): number | undefined };
2
3
  import FASTLY_CONFIG_SCHEMA from './config-schema.json';
3
4
 
@@ -24,6 +25,8 @@ export interface PulseFastlyLocalOptions {
24
25
  }
25
26
 
26
27
  export interface PulseFastlyProviderOptions {
28
+ /** Inclusive final Native Wasm byte budget; default 4 MiB. */
29
+ readonly maxWasmBytes?: number;
27
30
  readonly maxDurationMs?: number;
28
31
  readonly configStore?: string;
29
32
  readonly secretStore?: string;
@@ -60,6 +63,7 @@ export interface PulseFastlyProviderConfig {
60
63
  readonly dynamicBackends: boolean;
61
64
  };
62
65
  readonly build: {
66
+ readonly maxWasmBytes: number;
63
67
  readonly name: string;
64
68
  readonly description: string;
65
69
  readonly authors: readonly string[];
@@ -111,6 +115,7 @@ export function fastly(options: PulseFastlyProviderOptions = {}): PulseFastlyPro
111
115
  dynamicBackends: options.dynamicBackends === undefined ? Boolean(fastlyDefaults['fastly.dynamicBackends']) : options.dynamicBackends === true
112
116
  }),
113
117
  build: Object.freeze({
118
+ maxWasmBytes: normalizeFastlyMaxWasmBytes(options.maxWasmBytes),
114
119
  name: String(options.name ?? fastlyDefaults['fastly.name']),
115
120
  description: String(options.description ?? fastlyDefaults['fastly.description']),
116
121
  authors: Object.freeze((options.authors ?? (Array.isArray(fastlyDefaults['fastly.authors']) ? fastlyDefaults['fastly.authors'] : [])).map(String)),
@@ -1,10 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
+ normalizeJwtSignEffect,
4
5
  normalizeJwtVerifyEffect
5
6
  } = require('@pulse-compute/wasm-contracts/jwt/contracts');
6
7
  const {
7
8
  CRYPTO_ES256_RUNTIME_BUILTIN_IMPLEMENTATION,
9
+ CRYPTO_RS256_RUNTIME_BUILTIN_IMPLEMENTATION,
8
10
  CRYPTO_RUNTIME_BUILTIN_IMPLEMENTATION
9
11
  } = require('@pulse-compute/wasm-contracts/crypto/contracts');
10
12
 
@@ -16,6 +18,7 @@ const FASTLY_JAVASCRIPT_JWT_REALIZATIONS = Object.freeze({
16
18
  implementation: CRYPTO_RUNTIME_BUILTIN_IMPLEMENTATION,
17
19
  automaticFallback: false
18
20
  }),
21
+ RS256: Object.freeze({ algorithm: 'RS256', realization: 'runtime-builtin', implementation: CRYPTO_RS256_RUNTIME_BUILTIN_IMPLEMENTATION, automaticFallback: false }),
19
22
  ES256: Object.freeze({
20
23
  algorithm: 'ES256',
21
24
  realization: 'runtime-builtin',
@@ -83,12 +86,19 @@ function createFastlyJavascriptJwtVerify(options = {}) {
83
86
  return async function verifyFastlyJavascriptJwt(effect, execution) {
84
87
  const { jwtProvider, cryptoProvider } = await loadRuntimeModules();
85
88
  assertRequestActive(execution, jwtProvider);
86
- const input = normalizeJwtVerifyEffect(effect);
87
- if (execution && typeof execution.registerRedactionValue === 'function') {
89
+ const signing = effect.operation === 'sign';
90
+ const input = signing ? normalizeJwtSignEffect(effect) : normalizeJwtVerifyEffect(effect);
91
+ if (!signing && execution && typeof execution.registerRedactionValue === 'function') {
88
92
  execution.registerRedactionValue(input.token);
89
93
  }
90
94
  assertRequestActive(execution, jwtProvider);
91
- const selectedCrypto = assertRuntimeBuiltin(cryptoProvider, jwtProvider);
95
+ const selectedCrypto = signing
96
+ ? (input.options.algorithm === 'RS256'
97
+ ? require('@pulse-compute/crypto/provider').bindJavascriptRs256Signer()
98
+ : input.options.algorithm === 'ES256'
99
+ ? require('@pulse-compute/crypto/provider').bindJavascriptEs256Signer()
100
+ : require('@pulse-compute/crypto/provider').bindJavascriptDigestMac(['HMAC-SHA256']))
101
+ : assertRuntimeBuiltin(cryptoProvider, jwtProvider);
92
102
  const host = {
93
103
  async captureWallClock() {
94
104
  assertRequestActive(execution, jwtProvider);
@@ -118,7 +128,7 @@ function createFastlyJavascriptJwtVerify(options = {}) {
118
128
  return result;
119
129
  };
120
130
  }
121
- const result = await jwtProvider.verifyJwtWithCrypto(
131
+ const result = await (signing ? jwtProvider.signJwtWithCrypto : jwtProvider.verifyJwtWithCrypto)(
122
132
  input,
123
133
  Object.freeze(host),
124
134
  selectedCrypto
@@ -35,7 +35,7 @@ function createFastlyJavascriptPackageEffectCapabilities(options = {}) {
35
35
  }
36
36
  return gripBroadcast(effect.payload, execution);
37
37
  }
38
- if (effect && effect.contractId === 'pulse.jwt' && effect.operation === 'verify') {
38
+ if (effect && effect.contractId === 'pulse.jwt' && ['verify', 'sign'].includes(effect.operation)) {
39
39
  if (typeof jwtVerify !== 'function') return unavailable(effect);
40
40
  return jwtVerify(effect, execution);
41
41
  }
@@ -16,9 +16,11 @@ function blockedRestriction(id, restriction) {
16
16
 
17
17
  function classifyFastlyJavascriptCapability(id, restrictions = {}) {
18
18
  const capability = String(id);
19
+ if (capability === 'request.body.transform') return decision(capability, 'blocked', 'fastly-body-transform-unavailable', 'provider-fastly');
20
+ if (capability === 'request.body.forward' || capability === 'req.body') return decision(capability, 'blocked', 'fastly-incoming-body-forwarding-unavailable', 'provider-fastly');
19
21
  if (capability === 'crypto.digestText') return decision(capability, 'eligible', 'fastly-javascript-crypto-sha256-runtime-builtin', 'provider-fastly');
20
22
  if (['kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap'].includes(capability)) return decision(capability, 'blocked', 'fastly-conditional-kv-incomplete', 'provider-fastly');
21
- if (['s3.head', 's3.getText', 's3.putText'].includes(capability)) return decision(capability, 'blocked', 'fastly-javascript-s3-raw-headers-unavailable', 'provider-fastly');
23
+ if (['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(capability)) return decision(capability, 'blocked', 'fastly-javascript-s3-raw-headers-unavailable', 'provider-fastly');
22
24
  if (isJavascriptCoreCapability(capability)) {
23
25
  return decision(capability, 'eligible', 'fastly-request-router-runtime', 'provider-fastly');
24
26
  }
@@ -82,9 +84,11 @@ function classifyFastlyJavascriptCapability(id, restrictions = {}) {
82
84
  return decision(capability, 'eligible', 'fastly-javascript-provider-wall-clock-authority', 'provider-fastly');
83
85
  }
84
86
  if (
85
- capability === 'jwt.verify'
87
+ capability === 'jwt.sign'
88
+ || capability === 'jwt.verify'
86
89
  || capability === 'jwt.verify.hs256'
87
90
  || capability === 'jwt.verify.es256'
91
+ || capability === 'jwt.verify.rs256'
88
92
  ) {
89
93
  return blockedRestriction(capability, restrictions.jwt)
90
94
  || decision(capability, 'eligible', 'fastly-javascript-jwt-crypto-runtime-builtin', 'provider-fastly');
@@ -105,7 +109,7 @@ function classifyFastlyJavascriptProviderRequirement(id, restrictions = {}) {
105
109
  const requirement = String(id);
106
110
  if (requirement === 'crypto.digestText') return decision(requirement, 'eligible', 'fastly-javascript-crypto-sha256-runtime-builtin', 'provider-fastly');
107
111
  if (['kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap'].includes(requirement)) return decision(requirement, 'blocked', 'fastly-conditional-kv-incomplete', 'provider-fastly');
108
- if (['s3.head', 's3.getText', 's3.putText'].includes(requirement)) return decision(requirement, 'blocked', 'fastly-javascript-s3-raw-headers-unavailable', 'provider-fastly');
112
+ if (['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(requirement)) return decision(requirement, 'blocked', 'fastly-javascript-s3-raw-headers-unavailable', 'provider-fastly');
109
113
  if (requirement === 'request' || ['response.json', 'response.text', 'response.custom'].includes(requirement)) {
110
114
  return decision(requirement, 'eligible', 'fastly-request-response-lifecycle', 'provider-fastly');
111
115
  }
@@ -147,9 +151,11 @@ function classifyFastlyJavascriptProviderRequirement(id, restrictions = {}) {
147
151
  return decision(requirement, 'eligible', 'fastly-javascript-provider-wall-clock-authority', 'provider-fastly');
148
152
  }
149
153
  if (
150
- requirement === 'jwt.verify'
154
+ requirement === 'jwt.sign'
155
+ || requirement === 'jwt.verify'
151
156
  || requirement === 'jwt.verify.hs256'
152
157
  || requirement === 'jwt.verify.es256'
158
+ || requirement === 'jwt.verify.rs256'
153
159
  ) {
154
160
  return blockedRestriction(requirement, restrictions.jwt)
155
161
  || decision(requirement, 'eligible', 'fastly-javascript-jwt-crypto-runtime-builtin', 'provider-fastly');
@@ -188,7 +194,7 @@ function fastlyJavascriptProjectRestrictions(compiled, project) {
188
194
  const secretSites = sites.filter((entry) => entry.kind === 'secret.get');
189
195
  const jwtSites = sites.filter((entry) => (
190
196
  entry.contractId === 'pulse.jwt'
191
- && (entry.operation === 'verify' || entry.kind === 'jwt.verify')
197
+ && (['verify', 'sign'].includes(entry.operation) || ['jwt.verify', 'jwt.sign'].includes(entry.kind))
192
198
  ));
193
199
  const output = {};
194
200
 
@@ -14,6 +14,11 @@ const FASTLY_JAVASCRIPT_TARGET_DESCRIPTOR = normalizeJavascriptTargetDescriptor(
14
14
  crypto: defineCryptoTargetCapabilities({
15
15
  target: 'javascript',
16
16
  algorithms: [{
17
+ algorithm: 'HMAC-SHA256',
18
+ realization: 'runtime-builtin',
19
+ implemented: true,
20
+ status: 'implemented-jwt-sign'
21
+ }, {
17
22
  algorithm: 'SHA-256',
18
23
  realization: 'runtime-builtin',
19
24
  implemented: true,
@@ -28,6 +33,11 @@ const FASTLY_JAVASCRIPT_TARGET_DESCRIPTOR = normalizeJavascriptTargetDescriptor(
28
33
  realization: 'runtime-builtin',
29
34
  implemented: true,
30
35
  status: 'implemented-h4'
36
+ }, {
37
+ algorithm: 'RS256',
38
+ realization: 'runtime-builtin',
39
+ implemented: true,
40
+ status: 'implemented-h4'
31
41
  }]
32
42
  }),
33
43
  capabilities: {
@@ -14,8 +14,8 @@ const FASTLY_PROVIDER_DESCRIPTOR = canonical.normalizeDescriptor({
14
14
  capabilities: [
15
15
  'request', 'response.json', 'response.text', 'response.custom', 'fetch',
16
16
  'time.wall-clock', 'crypto.digestText', 'config.get', 'secret.get', 'kv.get', 'kv.put', 'kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap', 'assets.lookup',
17
- 'grip.channel', 'grip.hold', 'grip.publish', 'grip.broadcast', 'jwt.verify',
18
- 'opaque.pass-through', 's3.head', 's3.getText', 's3.putText'
17
+ 'grip.channel', 'grip.hold', 'grip.publish', 'grip.broadcast', 'jwt.verify', 'jwt.sign',
18
+ 'opaque.pass-through', 's3.head', 's3.getText', 's3.putText', 's3.getBody'
19
19
  ],
20
20
  lowering: {
21
21
  request: 'fastly.compute.request',
@@ -38,8 +38,10 @@ const FASTLY_PROVIDER_DESCRIPTOR = canonical.normalizeDescriptor({
38
38
  'grip.publish': 'fastly.fanout.publish',
39
39
  'grip.broadcast': 'fastly.grip.publish-control',
40
40
  'jwt.verify': 'fastly.native.jwt.verify',
41
+ 'jwt.sign': 'fastly.native.jwt.sign',
41
42
  's3.head': 'fastly.native.s3.head',
42
43
  's3.getText': 'fastly.native.s3.getText',
44
+ 's3.getBody': 'fastly.native.s3.getBody',
43
45
  's3.putText': 'fastly.native.s3.putText',
44
46
  'opaque.pass-through': 'fastly.response-body.stream'
45
47
  }
@@ -80,6 +80,7 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
80
80
  const bodies = new Map();
81
81
  const responses = new Map();
82
82
  const pending = new Map();
83
+ const outboundRequests = [];
83
84
  const downstreamRequestHandle = 1;
84
85
  const downstreamBodyHandle = 2;
85
86
  requests.set(downstreamRequestHandle, { method, url, headers: requestHeaders.map((entry) => [...entry]) });
@@ -230,7 +231,9 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
230
231
  return FASTLY_STATUS_ERROR;
231
232
  }
232
233
  const pendingHandle = alloc('pending');
233
- pending.set(pendingHandle, { request: { ...request, headers: request.headers.map((entry) => [...entry]), body: bodyBytes(bodyHandle) }, backend, fixture });
234
+ const outbound = { ...request, headers: request.headers.map((entry) => [...entry]), body: bodyBytes(bodyHandle) };
235
+ outboundRequests.push(outbound);
236
+ pending.set(pendingHandle, { request: outbound, backend, fixture });
234
237
  writeU32(pendingOut, pendingHandle);
235
238
  trace.push({ module: 'fastly_http_req', name: 'send_async', handle: Number(handle), bodyHandle: Number(bodyHandle), pendingHandle, backend, method: request.method, url: request.url });
236
239
  return FASTLY_STATUS_OK;
@@ -373,6 +376,7 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
373
376
  bodyHandle: downstream.bodyHandle
374
377
  }),
375
378
  trace: Object.freeze(trace.map((entry) => Object.freeze({ ...entry }))),
379
+ outboundRequests: Object.freeze(outboundRequests.map(entry => Object.freeze(entry))),
376
380
  instance
377
381
  });
378
382
  }
@@ -204,6 +204,15 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
204
204
  body = JSON.stringify(normalized.json);
205
205
  if (!headers.some(([name]) => name.toLowerCase() === 'content-type')) headers.push(['content-type', 'application/json; charset=utf-8']);
206
206
  }
207
+ // Ordinary Pulse harness fixtures use value/text; preserve the ABI fixture
208
+ // body's explicit precedence and its existing json shorthand.
209
+ if (body === undefined && Object.prototype.hasOwnProperty.call(normalized, 'value')) {
210
+ body = JSON.stringify(normalized.value);
211
+ if (!headers.some(([name]) => name.toLowerCase() === 'content-type')) headers.push(['content-type', 'application/json; charset=utf-8']);
212
+ } else if (body === undefined && Object.prototype.hasOwnProperty.call(normalized, 'text')) {
213
+ body = String(normalized.text);
214
+ if (!headers.some(([name]) => name.toLowerCase() === 'content-type')) headers.push(['content-type', 'text/plain; charset=utf-8']);
215
+ }
207
216
  responses.set(responseHandle, {
208
217
  status: Number(normalized.status || 200),
209
218
  headers,
@@ -607,7 +616,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
607
616
  }
608
617
  },
609
618
  fastly_http_body: {
610
- close(handle) { trace.push({ module: 'fastly_http_body', name: 'close' }); return bodies.delete(Number(handle)) ? FASTLY_STATUS_OK : FASTLY_STATUS_BADF; },
619
+ close(handle) { trace.push({ module: 'fastly_http_body', name: 'close', handle: Number(handle) }); if (downstream?.streaming && downstream.bodyHandle === Number(handle)) { downstream.bodyBytes = bodyBytes(handle); downstream.finished = true; } return bodies.delete(Number(handle)) ? FASTLY_STATUS_OK : FASTLY_STATUS_BADF; },
611
620
  new(handleOut) {
612
621
  const handle = alloc('body');
613
622
  bodies.set(handle, { bytes: Buffer.alloc(0), readOffset: 0, writes: [] });
@@ -632,6 +641,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
632
641
  write(handle, buffer, bufferLength, end, writtenOut) {
633
642
  const body = bodies.get(Number(handle));
634
643
  if (!body) return FASTLY_STATUS_BADF;
644
+ if (downstream?.streaming && Number(handle) === downstream.bodyHandle && options.downstreamBodyWriteStatus) return Number(options.downstreamBodyWriteStatus);
635
645
  if (requests.size > 1 && options.outboundBodyWriteStatus) return Number(options.outboundBodyWriteStatus);
636
646
  const length = Math.min(Number(bufferLength), Number(options.bodyWriteChunkBytes || bufferLength));
637
647
  const bytes = readBytes(buffer, length);
@@ -665,7 +675,16 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
665
675
  throw new FastlyNativePlatformCapabilitiesMockError('Pass98 native module is missing _start.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_MOCK_START_MISSING');
666
676
  }
667
677
  if (options.conditionalKv && options.conditionalKv.deadlineNs !== undefined) instance.exports.pulse_fastly_kv_request_deadline(BigInt(options.conditionalKv.deadlineNs));
668
- instance.exports._start();
678
+ try { instance.exports._start(); }
679
+ catch (error) {
680
+ if (Number(instance.exports.pulse_fastly_last_error?.()) !== 1010) throw error;
681
+ throw new FastlyNativePlatformCapabilitiesMockError('Native retained-value budget exceeded.',
682
+ 'PULSE_RUNTIME_MEMORY_LIMIT_EXCEEDED', {
683
+ lastError: 1010, errorStage: Number(instance.exports.pulse_fastly_error_stage()), trace,
684
+ bytes: Number(instance.exports.pulse_fastly_memory_bytes()),
685
+ values: Number(instance.exports.pulse_fastly_memory_values())
686
+ });
687
+ }
669
688
  for (const entry of trace) if (entry.url && privateUrls.has(entry.url)) entry.url = '[REDACTED]';
670
689
  const lastError = typeof instance.exports.pulse_fastly_last_error === 'function'
671
690
  ? Number(instance.exports.pulse_fastly_last_error())
@@ -679,10 +698,11 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
679
698
  const pulseLastError = typeof instance.exports.pulse_last_error_code === 'function'
680
699
  ? Number(instance.exports.pulse_last_error_code())
681
700
  : undefined;
701
+ if (downstream?.streaming && !downstream.finished) { downstream.bodyBytes = bodyBytes(downstream.bodyHandle); downstream.aborted = true; }
682
702
  const handledJwtError = options.allowHandledJwtError === true
683
703
  && lastError === 1008
684
704
  && Boolean(downstream);
685
- if (lastError !== FASTLY_STATUS_OK && !handledJwtError) {
705
+ if (lastError !== FASTLY_STATUS_OK && !handledJwtError && !(options.allowAbortedResponse === true && downstream?.aborted)) {
686
706
  throw new FastlyNativePlatformCapabilitiesMockError('Fastly native platform capabilities module reported an execution error.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_MOCK_EXECUTION_FAILED', { lastError, errorStage, errorEffect, pulseLastError, trace });
687
707
  }
688
708
  if (!downstream) {
@@ -699,6 +719,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
699
719
  bodyBytes: downstream.bodyBytes,
700
720
  sent: true,
701
721
  streaming: downstream.streaming,
722
+ aborted: downstream.aborted === true,
702
723
  origin: downstream.origin,
703
724
  responseHandle: downstream.responseHandle,
704
725
  bodyHandle: downstream.bodyHandle
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ const { normalizeFastlyMaxWasmBytes } = require('../build/wasm-budget.js');
2
3
  const { normalizeFastlyS3 } = require('./s3.js');
3
4
 
4
5
  function providerConfigError(code, message, detail) {
@@ -55,6 +56,7 @@ function normalizeFastlyProviderConfig(value) {
55
56
  dynamicBackends: bindings.dynamicBackends === true
56
57
  }),
57
58
  build: Object.freeze({
59
+ maxWasmBytes: normalizeFastlyMaxWasmBytes(input.build?.maxWasmBytes),
58
60
  name: String(input.build && input.build.name || 'pulse-app'),
59
61
  description: String(input.build && input.build.description || 'Pulse application'),
60
62
  authors: Object.freeze(Array.isArray(input.build && input.build.authors) ? input.build.authors.map(String) : [])
@@ -102,6 +104,8 @@ const FASTLY_PROJECT_CONFIG_REFERENCE = Object.freeze({
102
104
  })
103
105
  ]),
104
106
  fields: Object.freeze([
107
+ Object.freeze({section:'fastly',path:'fastly.maxWasmBytes',type:'integer',allowed:'positive safe integer',default:'4194304 (4 MiB)',scope:'fastly() facade',description:'Facade option projected to build.maxWasmBytes. Conventional profiles configure fastly.build.maxWasmBytes.'}),
108
+ Object.freeze({section:'fastly',path:'fastly.build.maxWasmBytes',type:'integer',allowed:'positive safe integer',default:'4194304 (4 MiB)',scope:'Native final Wasm artifact',description:'Inclusive byte budget checked for newly compiled and reused artifacts. Raising it does not increase runtime memory or change Fastly service limits. The fastly() facade accepts maxWasmBytes.'}),
105
109
  Object.freeze({section:'fastly',path:'fastly.maxDurationMs',type:'integer',allowed:'1–30000',default:'omitted',scope:'HTTP request execution',description:'One provider-owned monotonic budget through buffered response handoff; expiry attempts 504 before headers commit and never asserts rollback of dispatched writes. No CPU preemption or post-handoff delivery guarantee.'}),
106
110
  Object.freeze({ section: 'fastly', path: 'fastly.bindings.s3', type: 'Readonly<Record<string, S3Binding & { backend: string }>>', default: '`{}`', scope: 'Fastly Native S3', description: 'Maps logical names to fixed HTTPS endpoint, bucket, region, named static backend, accessKeyIdSecret, secretAccessKeySecret, optional sessionTokenSecret, maxTextBytes (1–2097152, default 32768) and timeoutMs (1–30000, default 10000).', security: 'Credentials resolve through the configured Secret Store at execution. Dynamic backend authority and guest endpoint overrides are forbidden.' }),
107
111
  Object.freeze({ section: 'fastly', path: 'fastly.configStore', type: 'string', default: '`pulse_config`', scope: 'Fastly config capability', description: 'Fastly Config Store resource name.' }),
@@ -169,16 +169,25 @@ function createDriver() {
169
169
  runtimeClass: 'native',
170
170
  status: 'supported',
171
171
  capabilities: Object.freeze([
172
- 's3.head', 's3.getText',
172
+ 's3.head', 's3.getText', 's3.getBody',
173
173
  'kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap',
174
174
  'jwt.verify',
175
+ 'jwt.sign',
175
176
  'jwt.verify.hs256',
176
177
  'jwt.verify.es256',
178
+ 'jwt.verify.rs256',
177
179
  'secret.get',
178
180
  'time.wall-clock'
179
181
  ]),
180
182
  keyTypes: Object.freeze(['secret', 'jwk', 'jwks']),
181
183
  realizations: Object.freeze([
184
+ Object.freeze({
185
+ kind: 'package-native-application',
186
+ contractId: 'pulse.entities',
187
+ implementation: 'pulse.package-native-application.v1',
188
+ implemented: true,
189
+ automaticFallback: false
190
+ }),
182
191
  Object.freeze({
183
192
  kind: 'crypto-composed',
184
193
  realization: 'guest-source:pulse-hmac-as',
@@ -204,6 +213,19 @@ function createDriver() {
204
213
  guestUnitRequired: true,
205
214
  portable: true,
206
215
  automaticFallback: false
216
+ }),
217
+ Object.freeze({
218
+ kind: 'crypto-composed',
219
+ realization: 'guest-linked:pulse-rs256-bearssl-i31',
220
+ implementation: 'bearssl.0.6.rsa-i31.sha256.v1',
221
+ algorithms: Object.freeze(['RS256']),
222
+ keyTypes: Object.freeze(['jwk', 'jwks']),
223
+ implemented: true,
224
+ status: 'implemented-g4',
225
+ semanticOwner: '@pulse-compute/crypto',
226
+ guestUnitRequired: true,
227
+ portable: true,
228
+ automaticFallback: false
207
229
  })
208
230
  ]),
209
231
  jwt: Object.freeze({
@@ -233,6 +255,11 @@ function createDriver() {
233
255
  realization: 'guest-linked:pulse-es256-rustcrypto-p256',
234
256
  implemented: true,
235
257
  status: 'implemented-g4'
258
+ }, {
259
+ algorithm: 'RS256',
260
+ realization: 'guest-linked:pulse-rs256-bearssl-i31',
261
+ implemented: true,
262
+ status: 'implemented-g4'
236
263
  }]
237
264
  }),
238
265
  finalWasmPolicy: fastlyFinalWasmPolicy,
@@ -258,6 +285,7 @@ function createDriver() {
258
285
  realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
259
286
  guestUnits: invocation.nativeArtifact.guestUnits,
260
287
  nativeOptimization: invocation.optimization,
288
+ emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
261
289
  compileTimeoutMs: invocation.timeoutMs
262
290
  });
263
291
  const { native, ...publicRealization } = realization;
@@ -286,6 +314,7 @@ function createDriver() {
286
314
  realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
287
315
  guestUnits: invocation.nativeArtifact.guestUnits,
288
316
  nativeOptimization: invocation.optimization,
317
+ emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
289
318
  compileTimeoutMs: invocation.timeoutMs
290
319
  });
291
320
  const providerBuild = writeFastlyCanonicalTarget({
@@ -18,6 +18,7 @@ function prepareFastlyNativeExecution(invocation) {
18
18
  realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
19
19
  guestUnits: invocation.nativeArtifact.guestUnits,
20
20
  nativeOptimization: invocation.optimization,
21
+ emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
21
22
  compileTimeoutMs: invocation.timeoutMs || undefined
22
23
  });
23
24
  const evidence = Object.freeze({
@@ -17,10 +17,10 @@ function normalizeFastlyS3(input = {}) {
17
17
  function resolveFastlyS3(effects, input) {
18
18
  const configured = normalizeFastlyS3(input);
19
19
  return Object.freeze(effects.flatMap((effect, index) => {
20
- if (!['s3.head', 's3.getText', 's3.putText'].includes(effect.kind)) return [];
20
+ if (!['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind)) return [];
21
21
  const name = effect.resource && effect.resource.binding;
22
22
  if (!Object.hasOwn(configured, name)) throw new TypeError('The required Fastly S3 binding is missing.');
23
- return [Object.freeze({ index, ...configured[name], contentType: effect.operation === 'putText' ? require('@pulse-compute/s3/provider').normalizePutOptions({ contentType: effect.payload && effect.payload.contentType }).contentType : '' })];
23
+ return [Object.freeze({ index, ...configured[name], ...(effect.operation === 'getBody' ? require('@pulse-compute/s3/provider').normalizeBodyOptions(Object.fromEntries(['method', 'range', 'ifNoneMatch'].filter(key => effect.payload?.[key] !== undefined).map(key => [key, effect.payload[key]]))) : {}), contentType: effect.operation === 'putText' ? require('@pulse-compute/s3/provider').normalizePutOptions({ contentType: effect.payload && effect.payload.contentType }).contentType : '' })];
24
24
  }));
25
25
  }
26
26
  module.exports = { normalizeFastlyS3, resolveFastlyS3 };