@pulse-compute/provider-fastly 1.0.0-beta.10 → 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 (36) hide show
  1. package/README.md +6 -6
  2. package/package.json +11 -11
  3. package/src/build/canonical-target.js +6 -0
  4. package/src/build/effect-invocations.js +6 -4
  5. package/src/build/host-extensions.js +254 -0
  6. package/src/build/lifecycle.js +50 -0
  7. package/src/build/native-body-forwarding.as.ts +28 -44
  8. package/src/build/native-body-forwarding.js +15 -11
  9. package/src/build/native-fetch-accounting.as.ts +133 -0
  10. package/src/build/native-fetch-accounting.js +84 -0
  11. package/src/build/native-fetch-bounds.js +47 -0
  12. package/src/build/native-fetch-cohorts.as.ts +126 -0
  13. package/src/build/native-fetch-cohorts.js +65 -0
  14. package/src/build/native-fetch-deadlines.as.ts +13 -8
  15. package/src/build/native-fetch-deadlines.js +13 -4
  16. package/src/build/native-fetch-errors.js +2 -2
  17. package/src/build/native-fetch-headers.js +52 -0
  18. package/src/build/native-fetch-outcomes.js +87 -0
  19. package/src/build/native-fetch-timing.as.ts +74 -0
  20. package/src/build/native-fetch-timing.js +50 -0
  21. package/src/build/native-forwarding-accounting.as.ts +153 -0
  22. package/src/build/native-forwarding-accounting.js +47 -0
  23. package/src/build/native-header-lookup.js +2 -2
  24. package/src/build/native-http-effects.js +10 -4
  25. package/src/build/native-platform-capabilities.js +89 -39
  26. package/src/build/native-request-body.js +6 -5
  27. package/src/build/{request-budget.as.ts → request-budget-source.js} +12 -4
  28. package/src/build/request-budget.js +7 -8
  29. package/src/build/response-authority.js +56 -0
  30. package/src/javascript/fetch-adapter.js +3 -0
  31. package/src/javascript/runtime-host.js +1 -0
  32. package/src/provider-contract.js +2 -1
  33. package/src/testing/native-http-effects-host.js +9 -0
  34. package/src/testing/native-platform-capabilities-host.js +10 -0
  35. package/src/toolchain/index.js +2 -1
  36. package/src/toolchain/native-execution.js +7 -2
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ // Adapted from HEX research ecc36468b5900a0de57a415379b85d12c5026f3f; structural emission.
4
+
5
+ const { httpLifecycle } = require('@pulse-compute/wasm-contracts/provider/toolchain');
6
+ if (httpLifecycle.VERSION !== 'pulse.http-lifecycle.v1') throw bindingError('Unsupported HTTP lifecycle contract version.');
7
+ const { AUTHORITY } = httpLifecycle;
8
+ // Fastly owns this diagnostic/runtime encoding; shared contracts use names.
9
+ const STATES = Object.freeze({ owned: 0, 'outcome-unknown': 1, transferred: 2, completed: 3 });
10
+ if (AUTHORITY.states.length !== Object.keys(STATES).length || AUTHORITY.states.some(state => !Object.hasOwn(STATES, state)))
11
+ throw bindingError('Fastly response-authority encoding does not cover the shared contract.');
12
+ const dispatch = AUTHORITY.transitions.dispatch;
13
+ const transfer = AUTHORITY.transitions.transfer;
14
+ const complete = AUTHORITY.transitions.complete;
15
+ const mayRespond = AUTHORITY.mayRespond.map(state => `__pulse_response_authority == ${STATES[state]}`).join(' || ');
16
+
17
+ function bindingError(message) {
18
+ return Object.assign(new Error(message), { code: 'PULSE_FASTLY_LIFECYCLE_HOOK_INVALID' });
19
+ }
20
+
21
+ // Driver-owned downstream authority, selected only by services that need it.
22
+ // This is not resource ownership: streaming writers and borrowed request handles
23
+ // still require their service's qualified disposal policy.
24
+ function runtimeSource() {
25
+ return `
26
+ // 0 owned; 1 dispatch outcome unknown; 2 transferred; 3 locally completed.
27
+ // Completion is not a delivery receipt. Nonzero status or trap never reopens it.
28
+ let __pulse_response_authority: i32 = ${STATES[AUTHORITY.initial]}
29
+ function __pulse_response_owned(): bool { return ${mayRespond} }
30
+ function __pulse_response_begin(): bool {
31
+ if (!__pulse_response_owned()) { __pulse_fastly_fail(PULSE_ERROR_STATE, 282, -1); return false }
32
+ __pulse_response_authority = ${STATES[dispatch.to]}
33
+ return true
34
+ }
35
+ function __pulse_response_transferred(): void {
36
+ if (__pulse_response_authority == ${STATES[transfer.from]}) __pulse_response_authority = ${STATES[transfer.to]}
37
+ }
38
+ function __pulse_response_complete(): void {
39
+ if (__pulse_response_authority == ${STATES[complete.from]}) __pulse_response_authority = ${STATES[complete.to]}
40
+ }
41
+ function fastly_http_resp_send_downstream(handle: i32, body: i32, streaming: i32): i32 {
42
+ if (!__pulse_response_begin()) return PULSE_ERROR_STATE
43
+ const status = __pulse_response_send_raw(handle, body, streaming)
44
+ if (status == 0) {
45
+ __pulse_response_transferred()
46
+ if (streaming == 0) __pulse_response_complete()
47
+ }
48
+ return status
49
+ }
50
+ `;
51
+ }
52
+ function hooks() {
53
+ return [{ point: 'response.authority', owner: 'driver.response', order: -100,
54
+ body: ' return __pulse_response_owned()' }];
55
+ }
56
+ module.exports = { STATES, runtimeSource, hooks };
@@ -53,7 +53,9 @@ function createFastlyJavascriptFetchCapability(options = {}) {
53
53
  ...(configuredBackend ? { backend: configuredBackend } : {})
54
54
  };
55
55
  try {
56
+ execution.fetchTiming?.begin();
56
57
  const response = await implementation(String(url), requestInit);
58
+ execution.fetchTiming?.headers();
57
59
  if (!(response instanceof Response) && !runtimeHost.isPulseFetchResponse(response)) {
58
60
  throw fastlyJavascriptProviderError(
59
61
  'PULSE_FASTLY_FETCH_RESPONSE_INVALID',
@@ -63,6 +65,7 @@ function createFastlyJavascriptFetchCapability(options = {}) {
63
65
  }
64
66
  return response;
65
67
  } catch (error) {
68
+ execution.fetchTiming?.fail();
66
69
  if (isPulseRuntimeError(error)) throw error;
67
70
  if (requestInit.signal && requestInit.signal.aborted) {
68
71
  const reason = requestInit.signal.reason;
@@ -72,6 +72,7 @@ async function executeFastlyJavascriptApplication(application, request, options
72
72
  maxEffects: options.maxEffects,
73
73
  maxRequestBodyBytes: options.maxRequestBodyBytes,
74
74
  maxFetchBodyBytes: options.maxFetchBodyBytes,
75
+ fetchClock: options.fetchClock,
75
76
  maxStructuredBodyBytes: options.maxStructuredBodyBytes,
76
77
  maxBindingNameBytes: options.maxBindingNameBytes,
77
78
  maxBindingValueBytes: options.maxBindingValueBytes,
@@ -12,12 +12,13 @@ const FASTLY_PROVIDER_DESCRIPTOR = canonical.normalizeDescriptor({
12
12
  deployable: true,
13
13
  localExecution: true,
14
14
  capabilities: [
15
- 'request', 'request.body.forward', 'response.json', 'response.text', 'response.custom', 'fetch',
15
+ 'host.extension', 'request', 'request.body.forward', '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', 'kv.list', 'kv.delete', 'assets.lookup',
17
17
  'grip.channel', 'grip.hold', 'grip.publish', 'grip.broadcast', 'jwt.verify', 'jwt.sign',
18
18
  'opaque.pass-through', 's3.head', 's3.getText', 's3.putText', 's3.getBody'
19
19
  ],
20
20
  lowering: {
21
+ 'host.extension': 'fastly.native.terminal-request-backend.v1',
21
22
  request: 'fastly.compute.request',
22
23
  'request.body.forward': 'fastly.native.body-forwarding',
23
24
  'response.json': 'fastly.compute.response.json',
@@ -223,6 +223,15 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
223
223
  trace.push({ module: 'fastly_http_req', name: 'header_insert', handle: Number(handle), header: [name, value] });
224
224
  return FASTLY_STATUS_OK;
225
225
  },
226
+ header_append(handle, namePointer, nameLength, valuePointer, valueLength) {
227
+ const request = requests.get(Number(handle));
228
+ if (!request) return FASTLY_STATUS_BADF;
229
+ const name = readUtf8(namePointer, nameLength);
230
+ const value = readUtf8(valuePointer, valueLength);
231
+ request.headers.push([name, value]);
232
+ trace.push({ module: 'fastly_http_req', name: 'header_append', handle: Number(handle), header: [name, value] });
233
+ return FASTLY_STATUS_OK;
234
+ },
226
235
  send_async(handle, bodyHandle, backendPointer, backendLength, pendingOut) {
227
236
  const request = requests.get(Number(handle));
228
237
  const body = bodies.get(Number(bodyHandle));
@@ -483,6 +483,16 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
483
483
  trace.push({ module: 'fastly_http_req', name: 'header_insert', handle: Number(handle), header: redactRuntimeHeaders([[name, value]], privateUrls.has(request.url))[0] });
484
484
  return FASTLY_STATUS_OK;
485
485
  },
486
+ header_append(handle, namePointer, nameLength, valuePointer, valueLength) {
487
+ const request = requests.get(Number(handle));
488
+ if (!request) return FASTLY_STATUS_BADF;
489
+ const name = readUtf8(namePointer, nameLength);
490
+ const value = readUtf8(valuePointer, valueLength);
491
+ if (name.toLowerCase() === 'authorization' && value.includes('/s3/aws4_request')) privateUrls.add(request.url);
492
+ request.headers.push([name, value]);
493
+ trace.push({ module: 'fastly_http_req', name: 'header_append', handle: Number(handle), header: redactRuntimeHeaders([[name, value]], privateUrls.has(request.url))[0] });
494
+ return FASTLY_STATUS_OK;
495
+ },
486
496
  cache_override_set(handle, tag, ttl, swr) {
487
497
  const request = requests.get(Number(handle)); if (!request) return FASTLY_STATUS_BADF;
488
498
  request.cacheOverride = Number(tag); return FASTLY_STATUS_OK;
@@ -168,6 +168,7 @@ function createDriver() {
168
168
  version: 'pulse.provider-target-descriptor.v1',
169
169
  target: 'native',
170
170
  targetId: 'fastly-compute-native',
171
+ hostExtensions: require('../build/host-extensions.js').ADMISSION,
171
172
  runtimeClass: 'native',
172
173
  status: 'supported',
173
174
  capabilities: Object.freeze([
@@ -361,7 +362,7 @@ function createDriver() {
361
362
  providerPackage: providerBuild.packageFile,
362
363
  providerSourceEntry: providerBuild.sourceEntryFile
363
364
  }),
364
- build: { ...providerBuild.build, reportAttribution: native.reportAttribution, buildObservation: native.buildObservation },
365
+ build: { ...providerBuild.build, reportAttribution: native.reportAttribution, hostExtensions: native.manifest.hostExtensions, buildObservation: native.buildObservation },
365
366
  realization: publicRealization,
366
367
  packaging: audit ? Object.freeze({
367
368
  version: PROVIDER_PACKAGING_AUDIT_VERSION,
@@ -28,9 +28,11 @@ function prepareFastlyNativeExecution(invocation) {
28
28
  target: 'fastly-compute-native',
29
29
  wasmSha256: native.manifest.wasm.sha256,
30
30
  providerRealityValidated: false,
31
- effectObservations: 'unavailable'
31
+ effectObservations: 'unavailable',
32
+ kvFixture: Object.freeze({ lifetime: 'invocation', initialization: 'supplied-fixtures', crossRequestPersistence: false }),
33
+ notice: 'KV fixture state is seeded from supplied fixtures for each invocation; writes do not persist across requests. Use the Fastly CLI/Viceroy workflow for local provider persistence and contention checks; deployed-service acceptance is separate.'
32
34
  });
33
- return (options = {}) => {
35
+ const execute = (options = {}) => {
34
36
  if (options.liveFetch === true) throw new TypeError('Fastly Native fixture execution cannot perform live fetches.');
35
37
  const kvStores = {};
36
38
  for (const [logical, physical] of Object.entries(bindings.kv || {})) {
@@ -73,6 +75,9 @@ function prepareFastlyNativeExecution(invocation) {
73
75
  evidence
74
76
  });
75
77
  };
78
+ // Public observation already returned with execution results, available before
79
+ // the first request without running a probe or acquiring additional authority.
80
+ return Object.freeze(Object.assign(execute, { evidence }));
76
81
  }
77
82
 
78
83
  module.exports = { prepareFastlyNativeExecution };