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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +53 -1
  2. package/dist/config-schema.json +103 -0
  3. package/dist/index.d.ts +77 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +67 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +88 -5
  8. package/src/build/canonical-target.js +469 -0
  9. package/src/build/digest-native.js +25 -0
  10. package/src/build/effect-invocations.js +49 -0
  11. package/src/build/embedded-assets.as.ts +32 -0
  12. package/src/build/entities-native.js +559 -0
  13. package/src/build/fixed-memory-runtime.as.ts +148 -0
  14. package/src/build/kv-native.as.ts +331 -0
  15. package/src/build/kv-native.js +27 -0
  16. package/src/build/native-application-errors.js +159 -0
  17. package/src/build/native-binary-resources.js +30 -0
  18. package/src/build/native-body-forwarding.as.ts +341 -0
  19. package/src/build/native-body-forwarding.js +69 -0
  20. package/src/build/native-dynamic-backends.as.ts +115 -0
  21. package/src/build/native-dynamic-backends.js +52 -0
  22. package/src/build/native-fetch-deadlines.as.ts +164 -0
  23. package/src/build/native-fetch-deadlines.js +27 -0
  24. package/src/build/native-fetch-errors.js +38 -0
  25. package/src/build/native-handle-ownership.js +127 -0
  26. package/src/build/native-header-lookup.js +50 -0
  27. package/src/build/native-http-effects.js +994 -0
  28. package/src/build/native-http-shell.js +703 -0
  29. package/src/build/native-platform-capabilities.js +2654 -0
  30. package/src/build/native-request-body.js +44 -0
  31. package/src/build/native-request-headers.js +93 -0
  32. package/src/build/native-send-error-detail.as.ts +12 -0
  33. package/src/build/native-stdio.js +45 -0
  34. package/src/build/native-string-values.js +143 -0
  35. package/src/build/native-transport-details.as.ts +84 -0
  36. package/src/build/native-value-budget.js +90 -0
  37. package/src/build/package-native-application.js +114 -0
  38. package/src/build/request-budget.as.ts +50 -0
  39. package/src/build/request-budget.js +47 -0
  40. package/src/build/s3-body.as.ts +97 -0
  41. package/src/build/s3-native.as.ts +228 -0
  42. package/src/build/s3-native.js +30 -0
  43. package/src/build/schema-admission.js +83 -0
  44. package/src/build/schema-nested-json.js +93 -0
  45. package/src/build/schema-scalar-record.js +41 -0
  46. package/src/build/time-native.js +49 -0
  47. package/src/build/wasm-budget.js +28 -0
  48. package/src/compiler/assets-package-out-parity-proof.js +338 -0
  49. package/src/compiler/assets-provider-proof.js +499 -0
  50. package/src/compiler/config-provider-resolution.js +573 -0
  51. package/src/compiler/fastly-adapter.js +564 -0
  52. package/src/compiler/fastly-command-entry.js +390 -0
  53. package/src/compiler/fastly-config-secret-serve.js +323 -0
  54. package/src/compiler/fastly-hostcall-binding.js +598 -0
  55. package/src/compiler/fastly-readiness.js +477 -0
  56. package/src/compiler/index.js +40 -0
  57. package/src/compiler/lifecycle-parity-contract.js +70 -0
  58. package/src/compiler/lifecycle-parity-proof.js +260 -0
  59. package/src/compiler/route-handler-effect-proof.js +255 -0
  60. package/src/compiler/runtime-provider-kv.js +237 -0
  61. package/src/compiler/runtime-provider-shape.js +109 -0
  62. package/src/config-api.js +69 -0
  63. package/src/config-schema.d.ts +13 -0
  64. package/src/config-schema.js +27 -0
  65. package/src/config-schema.json +103 -0
  66. package/src/index.js +3 -0
  67. package/src/index.ts +143 -0
  68. package/src/javascript/assets-adapter.js +122 -0
  69. package/src/javascript/bindings-adapter.js +237 -0
  70. package/src/javascript/errors.js +26 -0
  71. package/src/javascript/fetch-adapter.js +93 -0
  72. package/src/javascript/grip-broadcast.js +261 -0
  73. package/src/javascript/jwt-verifier.js +147 -0
  74. package/src/javascript/lifecycle.js +55 -0
  75. package/src/javascript/local-loader.js +138 -0
  76. package/src/javascript/logging.js +35 -0
  77. package/src/javascript/package-effects.js +71 -0
  78. package/src/javascript/runtime-host.js +112 -0
  79. package/src/javascript/sha256.js +128 -0
  80. package/src/javascript/source-package.js +778 -0
  81. package/src/javascript/support.js +142 -0
  82. package/src/javascript/target-support-policy.js +277 -0
  83. package/src/javascript/target.js +89 -0
  84. package/src/javascript/test-runtime.js +408 -0
  85. package/src/provider-contract.js +88 -0
  86. package/src/runtime/assets-provider.js +464 -0
  87. package/src/runtime/canonical-api-runtime.js +603 -0
  88. package/src/runtime/index.js +28 -0
  89. package/src/runtime/provider-primitives-contract.js +272 -0
  90. package/src/runtime/provider-primitives.js +82 -0
  91. package/src/runtime/route-handler-effects.js +507 -0
  92. package/src/testing/conditional-kv-host.js +155 -0
  93. package/src/testing/fastly-cli.js +538 -0
  94. package/src/testing/native-dynamic-backends-host.js +39 -0
  95. package/src/testing/native-http-effects-host.js +398 -0
  96. package/src/testing/native-http-shell-host.js +190 -0
  97. package/src/testing/native-platform-capabilities-host.js +924 -0
  98. package/src/toolchain/body-forwarding.js +25 -0
  99. package/src/toolchain/config.js +144 -0
  100. package/src/toolchain/index.js +386 -0
  101. package/src/toolchain/logging.js +18 -0
  102. package/src/toolchain/native-execution.js +78 -0
  103. package/src/toolchain/s3.js +26 -0
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const MAX_BODY_BYTES = 1048576;
4
+ function forwardingError(message) {
5
+ const error = new TypeError(message);
6
+ error.code = 'PULSE_FASTLY_BODY_FORWARDING_INVALID';
7
+ return error;
8
+ }
9
+ function normalizeBodyForwarding(value, maxDurationMs) {
10
+ if (value === undefined) return undefined;
11
+ if (!value || typeof value !== 'object' || Array.isArray(value)
12
+ || Object.keys(value).some(key => !['requestMaxBytes', 'responseMaxBytes'].includes(key))) {
13
+ throw forwardingError('fastly.bodyForwarding requires requestMaxBytes and responseMaxBytes.');
14
+ }
15
+ for (const key of ['requestMaxBytes', 'responseMaxBytes']) {
16
+ if (!Number.isInteger(value[key]) || value[key] < 1 || value[key] > MAX_BODY_BYTES) {
17
+ throw forwardingError(`fastly.bodyForwarding.${key} must be an integer from 1 to ${MAX_BODY_BYTES}.`);
18
+ }
19
+ }
20
+ if (!Number.isInteger(maxDurationMs) || maxDurationMs < 1 || maxDurationMs > 30000) {
21
+ throw forwardingError('fastly.bodyForwarding requires maxDurationMs from 1 to 30000.');
22
+ }
23
+ return Object.freeze({requestMaxBytes: value.requestMaxBytes, responseMaxBytes: value.responseMaxBytes});
24
+ }
25
+ module.exports = {normalizeBodyForwarding, forwardingError, MAX_BODY_BYTES};
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+ const { normalizeFastlyMaxWasmBytes } = require('../build/wasm-budget.js');
3
+ const { normalizeFastlyS3 } = require('./s3.js');
4
+
5
+ function providerConfigError(code, message, detail) {
6
+ const error = new TypeError(message);
7
+ error.code = code;
8
+ error.detail = Object.freeze({ ...detail });
9
+ return error;
10
+ }
11
+
12
+ function normalizeStringMap(input, code) {
13
+ if (input === undefined) return Object.freeze({});
14
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
15
+ throw providerConfigError(code, 'Provider binding maps must be objects.', {
16
+ valueType: Array.isArray(input) ? 'array' : typeof input
17
+ });
18
+ }
19
+ return Object.freeze(Object.fromEntries(
20
+ Object.entries(input).map(([key, value]) => [String(key), String(value)])
21
+ ));
22
+ }
23
+
24
+ function normalizeFastlyProviderConfig(value) {
25
+ const input = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
26
+ const bindings = input.bindings && typeof input.bindings === 'object' && !Array.isArray(input.bindings)
27
+ ? input.bindings
28
+ : {};
29
+ const local = input.local && typeof input.local === 'object' && !Array.isArray(input.local)
30
+ ? input.local
31
+ : {};
32
+ const gripInput = bindings.grip && typeof bindings.grip === 'object' && !Array.isArray(bindings.grip)
33
+ ? bindings.grip
34
+ : {};
35
+ return Object.freeze({
36
+ kind: 'fastly',
37
+ bodyForwarding: require('./body-forwarding.js').normalizeBodyForwarding(input.bodyForwarding, input.maxDurationMs),
38
+ maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(input.maxDurationMs),
39
+ version: input.version ? String(input.version) : undefined,
40
+ bindings: Object.freeze({
41
+ ...(bindings.logEndpoint === undefined ? {} : { logEndpoint: require('./logging.js').normalizeFastlyLogEndpoint(bindings.logEndpoint) }),
42
+ configStore: String(bindings.configStore || 'pulse_config'),
43
+ secretStore: String(bindings.secretStore || 'pulse_secrets'),
44
+ kv: normalizeStringMap(bindings.kv, 'PULSE_FASTLY_KV_BINDINGS_INVALID'),
45
+ s3: normalizeFastlyS3(bindings.s3),
46
+ backends: normalizeStringMap(bindings.backends, 'PULSE_FASTLY_BACKEND_BINDINGS_INVALID'),
47
+ grip: Object.freeze({
48
+ fanoutBackend: gripInput.fanoutBackend === undefined ? undefined : String(gripInput.fanoutBackend),
49
+ publishEndpoint: gripInput.publishEndpoint === undefined ? undefined : String(gripInput.publishEndpoint),
50
+ publishUrl: gripInput.publishUrl === undefined ? undefined : String(gripInput.publishUrl),
51
+ publishBackend: gripInput.publishBackend === undefined ? undefined : String(gripInput.publishBackend),
52
+ authentication: gripInput.authentication === undefined ? undefined : Object.freeze({
53
+ scheme: String(gripInput.authentication && gripInput.authentication.scheme || 'bearer').toLowerCase(),
54
+ secretRef: String(gripInput.authentication && gripInput.authentication.secretRef || '')
55
+ }),
56
+ directHold: gripInput.directHold !== false
57
+ }),
58
+ dynamicBackends: bindings.dynamicBackends === true
59
+ }),
60
+ build: Object.freeze({
61
+ maxWasmBytes: normalizeFastlyMaxWasmBytes(input.build?.maxWasmBytes),
62
+ name: String(input.build && input.build.name || 'pulse-app'),
63
+ description: String(input.build && input.build.description || 'Pulse application'),
64
+ authors: Object.freeze(Array.isArray(input.build && input.build.authors) ? input.build.authors.map(String) : [])
65
+ }),
66
+ local: Object.freeze({ networkFetch: local.networkFetch === true }),
67
+ providerSpecificUserland: false
68
+ });
69
+ }
70
+
71
+ function fastlyProjectConfigDocument(config) {
72
+ return Object.freeze({
73
+ kind: 'fastly',
74
+ bindings: config.bindings,
75
+ maxDurationMs: config.maxDurationMs,
76
+ bodyForwarding: config.bodyForwarding,
77
+ build: config.build,
78
+ local: config.local,
79
+ providerSpecificUserland: false
80
+ });
81
+ }
82
+
83
+ function fastlyInitTemplate(name) {
84
+ return Object.freeze({
85
+ profileFragment: ` fastly: {
86
+ bindings: {
87
+ configStore: 'pulse_config',
88
+ secretStore: 'pulse_secrets',
89
+ kv: { sessions: 'sessions' },
90
+ dynamicBackends: false,
91
+ },
92
+ build: { name: '${name}' },
93
+ },
94
+ `,
95
+ dependencies: Object.freeze({
96
+ '@pulse-compute/provider-fastly': require('../../package.json').version
97
+ })
98
+ });
99
+ }
100
+
101
+ const FASTLY_PROJECT_CONFIG_REFERENCE = Object.freeze({
102
+ sections: Object.freeze([
103
+ Object.freeze({
104
+ id: 'fastly',
105
+ title: 'Fastly provider options',
106
+ description: 'Options passed to fastly(...) from @pulse-compute/provider-fastly.'
107
+ })
108
+ ]),
109
+ fields: Object.freeze([
110
+ Object.freeze({ section: 'fastly', path: 'fastly.logEndpoint', type: 'string', default: 'omitted (stdout)', scope: 'fastly() facade; conventional profiles use fastly.bindings.logEndpoint', description: 'One case-sensitive named production log stream, 1–255 UTF-8 bytes, excluding stdout, stderr, fst_managed_ names and control characters. Records above 65536 UTF-8 bytes are dropped. One lookup and at most one write per record; errors and zero/short writes are dropped without retry or stdout fallback. Host acceptance does not prove downstream delivery.', diagnostics: Object.freeze(['PULSE_FASTLY_LOG_ENDPOINT_INVALID']) }),
111
+ Object.freeze({section:'fastly',path:'fastly.bodyForwarding',type:'object',default:'omitted',scope:'experimental Fastly Native forwarding',description:'Opt in to one terminal opaque POST with inline req.body() and a static backend. Requires maxDurationMs and both byte limits. Final close hands queued delivery to Fastly; it is not a client receipt.',diagnostics:Object.freeze(['PULSE_FASTLY_BODY_FORWARDING_INVALID'])}),
112
+ ...['requestMaxBytes','responseMaxBytes'].map(name => Object.freeze({section:'fastly',path:`fastly.bodyForwarding.${name}`,type:'integer',allowed:'1–1048576',default:'required with bodyForwarding',scope:'experimental Fastly Native forwarding',description:'Independent actual-byte bound, including EOF verification; no unbounded default. Fixed 16 KiB raw scratch is reused across the sequential upload and response pumps.'})),
113
+ 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.'}),
114
+ 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.'}),
115
+ 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.'}),
116
+ 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.' }),
117
+ Object.freeze({ section: 'fastly', path: 'fastly.configStore', type: 'string', default: '`pulse_config`', scope: 'Fastly config capability', description: 'Fastly Config Store resource name.' }),
118
+ Object.freeze({ section: 'fastly', path: 'fastly.secretStore', type: 'string', default: '`pulse_secrets`', scope: 'Fastly secret capability', description: 'Fastly Secret Store resource name.', security: 'The name is build metadata; secret values are never embedded by this option.' }),
119
+ Object.freeze({ section: 'fastly', path: 'fastly.kv', type: 'Readonly<Record<string, string>>', default: '`{}`', scope: 'Fastly KV capability', description: 'Maps logical ctx.kv names to Fastly KV Store resource names.', diagnostics: Object.freeze(['PULSE_FASTLY_KV_BINDINGS_INVALID']) }),
120
+ Object.freeze({ section: 'fastly', path: 'fastly.backends', type: 'Readonly<Record<string, string>>', default: '`{}`', scope: 'Fastly fetch capability', description: 'Maps static absolute origin URLs to named Fastly backends.', security: 'Prefer explicit backends over dynamic origin authority.', diagnostics: Object.freeze(['PULSE_FASTLY_BACKEND_BINDINGS_INVALID', 'PULSE_FASTLY_BACKEND_REQUIRED']) }),
121
+ Object.freeze({ section: 'fastly', path: 'fastly.dynamicBackends', type: 'boolean', default: '`false`', scope: 'Fastly fetch capability', description: 'Allows undeclared outbound origins to use dynamic backends.', security: 'Opt in deliberately because it broadens outbound authority.', diagnostics: Object.freeze(['PULSE_FASTLY_BACKEND_REQUIRED']) }),
122
+ Object.freeze({ section: 'fastly', path: 'fastly.grip', type: 'object', default: 'omitted', scope: 'Fastly GRIP capability', description: 'Provider-owned bindings used by package-owned GRIP compatibility and canonical broadcast effects.' }),
123
+ Object.freeze({ section: 'fastly', path: 'fastly.grip.fanoutBackend', type: 'string', default: 'omitted', scope: 'GRIP hold', description: 'Named Fastly Fanout backend.', diagnostics: Object.freeze(['PULSE_FASTLY_GRIP_FANOUT_BACKEND_REQUIRED']) }),
124
+ Object.freeze({ section: 'fastly', path: 'fastly.grip.publishEndpoint', type: 'string', default: 'omitted', scope: 'GRIP broadcast', description: 'Provider-owned absolute publish/control endpoint; distinct from public gateway and ingress URLs.', diagnostics: Object.freeze(['PULSE_FASTLY_GRIP_PUBLISH_BINDING_REQUIRED']) }),
125
+ Object.freeze({ section: 'fastly', path: 'fastly.grip.publishUrl', type: 'string', default: 'omitted', scope: 'GRIP publish', description: 'Absolute publish endpoint URL.', diagnostics: Object.freeze(['PULSE_FASTLY_GRIP_PUBLISH_BINDING_REQUIRED']) }),
126
+ Object.freeze({ section: 'fastly', path: 'fastly.grip.publishBackend', type: 'string', default: 'omitted', scope: 'GRIP broadcast/publish', description: 'Named backend for the provider publish endpoint.', diagnostics: Object.freeze(['PULSE_FASTLY_GRIP_PUBLISH_BINDING_REQUIRED']) }),
127
+ Object.freeze({ section: 'fastly', path: 'fastly.grip.authentication', type: 'object', default: 'omitted', scope: 'GRIP broadcast authentication', description: 'Authentication metadata containing references only; no credential value is accepted here.', security: 'Store the value in Fastly Secret Store and configure only secretRef.' }),
128
+ Object.freeze({ section: 'fastly', path: 'fastly.grip.authentication.scheme', type: "'bearer'", default: '`bearer`', scope: 'GRIP broadcast authentication', description: 'Provider authentication scheme.', diagnostics: Object.freeze(['PULSE_FASTLY_NATIVE_GRIP_AUTH_SCHEME_UNSUPPORTED']) }),
129
+ Object.freeze({ section: 'fastly', path: 'fastly.grip.authentication.secretRef', type: 'string', default: 'omitted', scope: 'GRIP broadcast authentication', description: 'Named Fastly Secret Store key used for the bearer token.', security: 'The secret value is resolved only at request execution and is redacted from traces.', diagnostics: Object.freeze(['PULSE_FASTLY_NATIVE_GRIP_SECRET_REFERENCE_MISSING']) }),
130
+ Object.freeze({ section: 'fastly', path: 'fastly.grip.directHold', type: 'boolean', default: '`true`', scope: 'GRIP hold', description: 'Enables the direct host-owned hold response realization.' }),
131
+ Object.freeze({ section: 'fastly', path: 'fastly.name', type: 'string', default: '`pulse-app`', scope: 'Fastly package metadata', description: 'Generated Fastly package name.' }),
132
+ Object.freeze({ section: 'fastly', path: 'fastly.description', type: 'string', default: '`Pulse application`', scope: 'Fastly package metadata', description: 'Generated Fastly package description.' }),
133
+ Object.freeze({ section: 'fastly', path: 'fastly.authors', type: 'readonly string[]', default: '`[]`', scope: 'Fastly package metadata', description: 'Generated Fastly package authors.' }),
134
+ Object.freeze({ section: 'fastly', path: 'fastly.local', type: 'object', default: '`{}`', scope: 'local Fastly conformance', description: 'Local-only behavior; it does not alter the deployed target.' }),
135
+ Object.freeze({ section: 'fastly', path: 'fastly.local.networkFetch', type: 'boolean', default: '`false`', scope: 'local Fastly fetch realization', description: 'Allows unmatched local Fastly fetches to use the network.', precedence: '`dev.networkFetch` overrides this value when explicitly set.', security: 'Keep disabled for deterministic tests and offline development.' })
136
+ ])
137
+ });
138
+
139
+ module.exports = Object.freeze({
140
+ normalizeFastlyProviderConfig,
141
+ fastlyProjectConfigDocument,
142
+ fastlyInitTemplate,
143
+ FASTLY_PROJECT_CONFIG_REFERENCE
144
+ });
@@ -0,0 +1,386 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const {
5
+ PROVIDER_DRIVER_VERSION,
6
+ PROVIDER_TARGET_RESULT_VERSION,
7
+ PROVIDER_PACKAGING_AUDIT_VERSION,
8
+ defineProviderToolchain,
9
+ defineProviderDriver,
10
+ normalizeProviderTargetResult,
11
+ normalizeProviderJavascriptPackageResult
12
+ } = require('@pulse-compute/wasm-contracts/provider/toolchain');
13
+ const { defineFinalWasmPolicy, FINAL_WASM_POLICY_VERSION } = require('@pulse-compute/wasm-contracts/provider/final-wasm-policy');
14
+ const { defineCryptoTargetCapabilities } = require('@pulse-compute/wasm-contracts/crypto/contracts');
15
+ const nativeRuntime = require('@pulse-compute/wasm-contracts/handler/canonical-native-runtime');
16
+ const fastlyRuntime = require('../runtime/canonical-api-runtime.js');
17
+ const { writeFastlyCanonicalTarget, inspectFastlyCanonicalTarget } = require('../build/canonical-target.js');
18
+ const { FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS } = require('../build/native-platform-capabilities.js');
19
+ const { FASTLY_PROVIDER_DESCRIPTOR, createFastlyLoweringPlan } = require('../provider-contract.js');
20
+ const { FASTLY_JAVASCRIPT_TARGET_DESCRIPTOR } = require('../javascript/target.js');
21
+ const { FASTLY_JAVASCRIPT_TARGET_SUPPORT_DECLARATION } = require('../javascript/support.js');
22
+ const {
23
+ classifyFastlyJavascriptCapability,
24
+ classifyFastlyJavascriptProviderRequirement,
25
+ fastlyJavascriptProjectRestrictions
26
+ } = require('../javascript/target-support-policy.js');
27
+ const fastlyJavascriptTestRuntime = require('../javascript/test-runtime.js');
28
+ const fastlyJavascriptLocalLoader = require('../javascript/local-loader.js');
29
+ const { writeFastlyJavascriptSourcePackage } = require('../javascript/source-package.js');
30
+ const {
31
+ normalizeFastlyProviderConfig,
32
+ fastlyProjectConfigDocument,
33
+ fastlyInitTemplate,
34
+ FASTLY_PROJECT_CONFIG_REFERENCE
35
+ } = require('./config.js');
36
+ const packageManifest = require('../../package.json');
37
+
38
+ const reportingTargetCapability = Object.freeze({
39
+ supported: true,
40
+ default: 'info',
41
+ levels: Object.freeze(['off', 'error', 'warn', 'info', 'debug']),
42
+ destinations: Object.freeze(['stdout', 'configured-named-endpoint'])
43
+ });
44
+
45
+ const fastlyFinalWasmPolicy = defineFinalWasmPolicy({
46
+ version: FINAL_WASM_POLICY_VERSION,
47
+ descriptorOwner: packageManifest.name,
48
+ toolchainVersion: 'pulse.provider-toolchain.v1',
49
+ descriptorIdentity: 'fastly-compute-native',
50
+ permittedImports: FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS
51
+ .map(([module, name]) => ({ module, name, kind: 'function' })),
52
+ requiredExports: nativeRuntime.CANONICAL_NATIVE_EXPORTS.map(([name, second]) => ({
53
+ name,
54
+ kind: second === 'memory' ? 'memory' : 'function'
55
+ }))
56
+ });
57
+
58
+ function adjustPackages(packages, restrictions) {
59
+ return packages.map((entry) => {
60
+ if (!entry.required) return entry;
61
+ const packageRestriction = entry.contractId === 'pulse.assets'
62
+ ? restrictions.assets
63
+ : (entry.contractId === 'pulse.grip' ? restrictions.grip : null);
64
+ if (!packageRestriction) return entry;
65
+ return Object.freeze({
66
+ ...entry,
67
+ status: 'blocked',
68
+ reasonId: packageRestriction.reasonId,
69
+ owner: packageRestriction.owner
70
+ });
71
+ });
72
+ }
73
+
74
+ function createDriver() {
75
+ return defineProviderDriver({
76
+ version: PROVIDER_DRIVER_VERSION,
77
+ id: 'fastly',
78
+ descriptor: FASTLY_PROVIDER_DESCRIPTOR,
79
+ executable: true,
80
+ localExecution: true,
81
+ deployable: true,
82
+ deploymentValidated: false,
83
+ sourcePackage: true,
84
+ compiledWasm: true,
85
+ defaultBuildMode: 'native-provider',
86
+ sourceOnlySupported: false,
87
+ targetSupport: Object.freeze({ javascript: FASTLY_JAVASCRIPT_TARGET_SUPPORT_DECLARATION }),
88
+ normalizeConfig: normalizeFastlyProviderConfig,
89
+ defaultLocalNetworkFetch: false,
90
+ projectConfigDocument: fastlyProjectConfigDocument,
91
+ configReference: FASTLY_PROJECT_CONFIG_REFERENCE,
92
+ initTemplate: fastlyInitTemplate,
93
+ javascript: Object.freeze({
94
+ kind: 'provider-bundled',
95
+ localEmulation: true,
96
+ buildTimeLoader: true,
97
+ localExecutionMode: fastlyJavascriptTestRuntime.FASTLY_JAVASCRIPT_LOCAL_EXECUTION_MODE,
98
+ realityRunner: 'fastly compute serve',
99
+ targetSupportPolicy: Object.freeze({
100
+ providerId: 'fastly',
101
+ declaration: FASTLY_JAVASCRIPT_TARGET_SUPPORT_DECLARATION,
102
+ context: fastlyJavascriptProjectRestrictions,
103
+ classifyCapability: classifyFastlyJavascriptCapability,
104
+ classifyProviderRequirement(id, _compilerCapabilities, restrictions) {
105
+ return classifyFastlyJavascriptProviderRequirement(id, restrictions);
106
+ },
107
+ packageOptions: Object.freeze({
108
+ suppliedCoreOwner: 'runtime',
109
+ packageResolutionReasonId: 'fastly-esbuild-package-resolution-planned',
110
+ packageResolutionOwner: 'provider-fastly',
111
+ packageRuntimeOwner: 'provider-fastly'
112
+ }),
113
+ adjustPackages,
114
+ commandOptions: Object.freeze({ projectScopedBuild: true })
115
+ }),
116
+ loadApplication(plan, options) {
117
+ return fastlyJavascriptLocalLoader.loadFastlyJavascriptLocalApplication(plan, {
118
+ projectRoot: options.projectRoot,
119
+ schemaBundle: options.schemaBundle
120
+ });
121
+ },
122
+ writeSourcePackage(invocation) {
123
+ return normalizeProviderJavascriptPackageResult(writeFastlyJavascriptSourcePackage({
124
+ plan: invocation.applicationPlan,
125
+ projectRoot: invocation.project.root,
126
+ outDir: invocation.project.outDir,
127
+ schemaBundle: invocation.javascript.schemaBundle,
128
+ binaryResources: invocation.javascript.binaryResources,
129
+ providerConfig: invocation.providerConfig,
130
+ reporting: invocation.javascript.reporting,
131
+ strict: invocation.javascript.strict,
132
+ maxStructuredBodyBytes: invocation.javascript.maxStructuredBodyBytes,
133
+ targetSupport: invocation.javascript.targetSupport
134
+ }), { provider: 'fastly' });
135
+ },
136
+ describeSourcePackage(sourcePackage, context) {
137
+ return Object.freeze({
138
+ deploymentCandidate: true,
139
+ downstreamJavascriptRuntimeWasm: true,
140
+ application: Object.freeze({
141
+ entry: path.relative(context.outDir, sourcePackage.paths.entry).replace(/\\/g, '/'),
142
+ modules: context.plan.graph.modules.filter((entry) => entry.runtime).length,
143
+ bundledApplication: path.relative(context.outDir, sourcePackage.paths.bundledApplication).replace(/\\/g, '/'),
144
+ deployment: path.basename(sourcePackage.paths.deployment),
145
+ deploymentCandidate: path.basename(sourcePackage.paths.deploymentCandidate),
146
+ fastlyToml: path.basename(sourcePackage.paths.fastlyToml),
147
+ buildConfig: path.basename(sourcePackage.paths.buildConfig),
148
+ bundledInputs: sourcePackage.manifest.summary.bundledInputs
149
+ }),
150
+ files: Object.freeze({
151
+ bundledApplication: sourcePackage.paths.bundledApplication,
152
+ deployment: sourcePackage.paths.deployment,
153
+ deploymentCandidate: sourcePackage.paths.deploymentCandidate,
154
+ fastlyToml: sourcePackage.paths.fastlyToml,
155
+ buildConfig: sourcePackage.paths.buildConfig
156
+ })
157
+ });
158
+ },
159
+ executeTestCase(application, testCase, options) {
160
+ return fastlyJavascriptTestRuntime.executeFastlyJavascriptTestCase(application, testCase, options);
161
+ },
162
+ createLocalEnvironment: fastlyJavascriptTestRuntime.createFastlyJavascriptLocalEnvironment,
163
+ createTestRequest: fastlyJavascriptTestRuntime.createFastlyJavascriptTestRequest,
164
+ executeLocalRequest: fastlyJavascriptTestRuntime.executeFastlyJavascriptLocalRequest
165
+ }),
166
+ targets: Object.freeze({
167
+ native: Object.freeze({
168
+ version: 'pulse.provider-target-descriptor.v1',
169
+ target: 'native',
170
+ targetId: 'fastly-compute-native',
171
+ runtimeClass: 'native',
172
+ status: 'supported',
173
+ capabilities: Object.freeze([
174
+ 's3.head', 's3.getText', 's3.getBody',
175
+ 'kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap', 'kv.list', 'kv.delete',
176
+ 'jwt.verify',
177
+ 'jwt.sign',
178
+ 'jwt.verify.hs256',
179
+ 'jwt.verify.es256',
180
+ 'jwt.verify.rs256',
181
+ 'secret.get',
182
+ 'time.wall-clock'
183
+ ]),
184
+ keyTypes: Object.freeze(['secret', 'jwk', 'jwks']),
185
+ realizations: Object.freeze([
186
+ Object.freeze({
187
+ kind: 'package-native-application',
188
+ contractId: 'pulse.entities',
189
+ implementation: 'pulse.package-native-application.v1',
190
+ implemented: true,
191
+ automaticFallback: false
192
+ }),
193
+ Object.freeze({
194
+ kind: 'package-native-application',
195
+ contractId: 'pulse.mcp',
196
+ implementation: 'pulse.package-native-application.v2',
197
+ implemented: true,
198
+ automaticFallback: false
199
+ }),
200
+ Object.freeze({
201
+ kind: 'crypto-composed',
202
+ realization: 'guest-source:pulse-hmac-as',
203
+ implementation: 'pulse-hmac-as.v1',
204
+ algorithms: Object.freeze(['HS256']),
205
+ keyTypes: Object.freeze(['secret']),
206
+ implemented: true,
207
+ status: 'implemented-e2',
208
+ semanticOwner: '@pulse-compute/crypto',
209
+ guestUnitRequired: false,
210
+ portable: true,
211
+ automaticFallback: false
212
+ }),
213
+ Object.freeze({
214
+ kind: 'crypto-composed',
215
+ realization: 'guest-linked:pulse-es256-rustcrypto-p256',
216
+ implementation: 'rustcrypto.p256-0.13.2.ecdsa-0.16.9.sha2-0.10.9.v1',
217
+ algorithms: Object.freeze(['ES256']),
218
+ keyTypes: Object.freeze(['jwk', 'jwks']),
219
+ implemented: true,
220
+ status: 'implemented-g4',
221
+ semanticOwner: '@pulse-compute/crypto',
222
+ guestUnitRequired: true,
223
+ portable: true,
224
+ automaticFallback: false
225
+ }),
226
+ Object.freeze({
227
+ kind: 'crypto-composed',
228
+ realization: 'guest-linked:pulse-rs256-bearssl-i31',
229
+ implementation: 'bearssl.0.6.rsa-i31.sha256.v1',
230
+ algorithms: Object.freeze(['RS256']),
231
+ keyTypes: Object.freeze(['jwk', 'jwks']),
232
+ implemented: true,
233
+ status: 'implemented-g4',
234
+ semanticOwner: '@pulse-compute/crypto',
235
+ guestUnitRequired: true,
236
+ portable: true,
237
+ automaticFallback: false
238
+ })
239
+ ]),
240
+ jwt: Object.freeze({
241
+ status: 'implemented-e2',
242
+ realization: 'guest-source:pulse-hmac-as',
243
+ implementation: 'pulse-hmac-as.v1',
244
+ packageSource: 'pulse-jwt-as',
245
+ providerAuthorities: Object.freeze([
246
+ 'request',
247
+ 'secret',
248
+ 'wall-clock',
249
+ 'schema',
250
+ 'error-transport'
251
+ ]),
252
+ automaticFallback: false
253
+ }),
254
+ automaticFallback: false,
255
+ crypto: defineCryptoTargetCapabilities({
256
+ target: 'native',
257
+ algorithms: [...['SHA-256', 'HMAC-SHA256'].map((algorithm) => ({ algorithm, realization: 'guest-source:pulse-hmac-as', implemented: true, status: 'implemented-o2' })), {
258
+ algorithm: 'HS256',
259
+ realization: 'guest-source:pulse-hmac-as',
260
+ implemented: true,
261
+ status: 'implemented-c3'
262
+ }, {
263
+ algorithm: 'ES256',
264
+ realization: 'guest-linked:pulse-es256-rustcrypto-p256',
265
+ implemented: true,
266
+ status: 'implemented-g4'
267
+ }, {
268
+ algorithm: 'RS256',
269
+ realization: 'guest-linked:pulse-rs256-bearssl-i31',
270
+ implemented: true,
271
+ status: 'implemented-g4'
272
+ }]
273
+ }),
274
+ finalWasmPolicy: fastlyFinalWasmPolicy,
275
+ reporting: reportingTargetCapability,
276
+ commands: Object.freeze({ compile: true, inspect: true, doctor: true, build: true, test: true, dev: true })
277
+ }),
278
+ javascript: FASTLY_JAVASCRIPT_TARGET_DESCRIPTOR
279
+ }),
280
+ execute: fastlyRuntime.executeCanonicalProgram,
281
+ prepareNativeExecution: require('./native-execution.js').prepareFastlyNativeExecution,
282
+ createLoweringPlan(metadata, providerConfig = {}) {
283
+ return createFastlyLoweringPlan(metadata, providerConfig.bindings || {}, providerConfig);
284
+ },
285
+ inspectRealization(invocation) {
286
+ const realization = inspectFastlyCanonicalTarget({
287
+ plan: invocation.applicationPlan,
288
+ providerPlan: invocation.providerPlan,
289
+ providerConfig: invocation.providerConfig,
290
+ projectRoot: invocation.project.root,
291
+ profile: invocation.project.profile,
292
+ targetDescriptor: invocation.selectedTarget,
293
+ synchronizedPackages: invocation.synchronizedPackages,
294
+ realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
295
+ guestUnits: invocation.nativeArtifact.guestUnits,
296
+ nativeOptimization: invocation.optimization,
297
+ elideJsonCacheArena: invocation.nativeArtifact.manifest.elideJsonCacheArena === true,
298
+ emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
299
+ compileTimeoutMs: invocation.timeoutMs
300
+ });
301
+ const { native, ...publicRealization } = realization;
302
+ return normalizeProviderTargetResult({
303
+ version: PROVIDER_TARGET_RESULT_VERSION,
304
+ action: 'inspect-native',
305
+ provider: 'fastly',
306
+ target: 'native',
307
+ status: 'inspected',
308
+ automaticFallback: false,
309
+ files: Object.freeze({}),
310
+ build: null,
311
+ realization: publicRealization,
312
+ packaging: null
313
+ });
314
+ },
315
+ writeTarget(invocation) {
316
+ const inspected = inspectFastlyCanonicalTarget({
317
+ plan: invocation.applicationPlan,
318
+ providerPlan: invocation.providerPlan,
319
+ providerConfig: invocation.providerConfig,
320
+ projectRoot: invocation.project.root,
321
+ profile: invocation.project.profile,
322
+ targetDescriptor: invocation.selectedTarget,
323
+ synchronizedPackages: invocation.synchronizedPackages,
324
+ realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
325
+ guestUnits: invocation.nativeArtifact.guestUnits,
326
+ nativeOptimization: invocation.optimization,
327
+ elideJsonCacheArena: invocation.nativeArtifact.manifest.elideJsonCacheArena === true,
328
+ emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
329
+ compileTimeoutMs: invocation.timeoutMs
330
+ });
331
+ const providerBuild = writeFastlyCanonicalTarget({
332
+ outDir: invocation.project.outDir,
333
+ plan: invocation.applicationPlan,
334
+ providerPlan: invocation.providerPlan,
335
+ native: inspected.native,
336
+ providerConfig: invocation.providerConfig,
337
+ bindings: invocation.providerConfig.bindings,
338
+ projectRoot: invocation.project.root,
339
+ profile: invocation.project.profile,
340
+ targetDescriptor: invocation.selectedTarget,
341
+ synchronizedPackages: invocation.synchronizedPackages
342
+ });
343
+ const { native, ...publicRealization } = inspected;
344
+ const audit = native.guestLink && native.guestLink.audit;
345
+ return normalizeProviderTargetResult({
346
+ version: PROVIDER_TARGET_RESULT_VERSION,
347
+ action: 'write-native',
348
+ provider: 'fastly',
349
+ target: 'native',
350
+ status: 'built',
351
+ automaticFallback: false,
352
+ files: Object.freeze({
353
+ provider: providerBuild.providerFile,
354
+ providerEntry: providerBuild.entryFile,
355
+ providerBuild: providerBuild.buildFile,
356
+ providerWasm: providerBuild.wasmFile,
357
+ providerWat: providerBuild.watFile,
358
+ providerNativePlan: providerBuild.nativePlanFile,
359
+ providerNativeManifest: providerBuild.nativeManifestFile,
360
+ fastlyToml: providerBuild.fastlyTomlFile,
361
+ providerPackage: providerBuild.packageFile,
362
+ providerSourceEntry: providerBuild.sourceEntryFile
363
+ }),
364
+ build: { ...providerBuild.build, reportAttribution: native.reportAttribution, buildObservation: native.buildObservation },
365
+ realization: publicRealization,
366
+ packaging: audit ? Object.freeze({
367
+ version: PROVIDER_PACKAGING_AUDIT_VERSION,
368
+ artifactSha256: providerBuild.build.wasm.sha256,
369
+ auditedSha256: audit.artifact.sha256,
370
+ authorized: native.guestLink.providerPackaging.authorized === true
371
+ }) : null
372
+ });
373
+ },
374
+ executionOptions(providerConfig = {}, values = {}) {
375
+ return { ...values, maxDurationMs: providerConfig.maxDurationMs, bindings: providerConfig.bindings };
376
+ }
377
+ });
378
+ }
379
+
380
+ module.exports = defineProviderToolchain({
381
+ version: 'pulse.provider-toolchain.v1',
382
+ id: 'fastly',
383
+ packageName: packageManifest.name,
384
+ packageVersion: packageManifest.version,
385
+ createDriver
386
+ });
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const MAX_LOG_BYTES = 65536;
4
+
5
+ function normalizeFastlyLogEndpoint(value) {
6
+ if (value === undefined) return undefined;
7
+ if (typeof value !== 'string' || !value || value.length > 255 || value.trim() !== value
8
+ || new TextEncoder().encode(value).byteLength > 255 || /[\u0000-\u001f\u007f]/u.test(value)
9
+ || /[\ud800-\udfff]/u.test(value.replace(/[\ud800-\udbff][\udc00-\udfff]/g, ''))
10
+ || /^(?:stdout$|stderr$|fst_managed_)/i.test(value)) {
11
+ const error = new TypeError('Fastly logEndpoint must be a non-reserved endpoint name of 1–255 UTF-8 bytes without control characters or surrounding whitespace.');
12
+ error.code = 'PULSE_FASTLY_LOG_ENDPOINT_INVALID';
13
+ throw error;
14
+ }
15
+ return value;
16
+ }
17
+
18
+ module.exports = { normalizeFastlyLogEndpoint, MAX_LOG_BYTES };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ const { inspectFastlyCanonicalTarget } = require('../build/canonical-target.js');
4
+ const { executeFastlyNativePlatformCapabilities } = require('../testing/native-platform-capabilities-host.js');
5
+ const { createConditionalKvAuthority } = require('../testing/conditional-kv-host.js');
6
+
7
+ // Execute the provider artifact against the existing fixture ABI. This is local
8
+ // conformance evidence, never Viceroy or deployed Fastly acceptance.
9
+ function prepareFastlyNativeExecution(invocation) {
10
+ const bindings = invocation.providerConfig.bindings;
11
+ const { native } = inspectFastlyCanonicalTarget({
12
+ plan: invocation.applicationPlan,
13
+ providerConfig: invocation.providerConfig,
14
+ projectRoot: invocation.project.root,
15
+ profile: invocation.project.profile,
16
+ targetDescriptor: invocation.selectedTarget,
17
+ synchronizedPackages: invocation.synchronizedPackages,
18
+ realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
19
+ guestUnits: invocation.nativeArtifact.guestUnits,
20
+ nativeOptimization: invocation.optimization,
21
+ elideJsonCacheArena: invocation.nativeArtifact.manifest.elideJsonCacheArena === true,
22
+ emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
23
+ compileTimeoutMs: invocation.timeoutMs || undefined
24
+ });
25
+ const evidence = Object.freeze({
26
+ kind: 'fastly-native-fixture-abi',
27
+ provider: 'fastly',
28
+ target: 'fastly-compute-native',
29
+ wasmSha256: native.manifest.wasm.sha256,
30
+ providerRealityValidated: false,
31
+ effectObservations: 'unavailable'
32
+ });
33
+ return (options = {}) => {
34
+ if (options.liveFetch === true) throw new TypeError('Fastly Native fixture execution cannot perform live fetches.');
35
+ const kvStores = {};
36
+ for (const [logical, physical] of Object.entries(bindings.kv || {})) {
37
+ kvStores[physical] = { ...kvStores[physical], ...(options.kv && options.kv[logical] || {}) };
38
+ }
39
+ const authority = createConditionalKvAuthority();
40
+ for (const [store, values] of Object.entries(kvStores)) {
41
+ authority.stores.set(store, new Map());
42
+ for (const [key, value] of Object.entries(values)) {
43
+ authority.seed(store, key, Buffer.from(JSON.stringify({ __pulseKv: 1, value })));
44
+ }
45
+ }
46
+ const execution = executeFastlyNativePlatformCapabilities(native, {
47
+ request: options.request,
48
+ fixtures: options.fetches,
49
+ configStore: bindings.configStore,
50
+ config: options.config,
51
+ secretStore: bindings.secretStore,
52
+ secrets: options.secrets,
53
+ kvStores,
54
+ conditionalKv: { authority }
55
+ });
56
+ const contentType = execution.response.headers.find(([name]) => name.toLowerCase() === 'content-type');
57
+ const type = String(contentType && contentType[1] || '').toLowerCase();
58
+ return Object.freeze({
59
+ response: Object.freeze({
60
+ ...execution.response,
61
+ kind: type.includes('application/json') || type.includes('+json') ? 'json' : type.startsWith('text/') ? 'text' : 'response',
62
+ // Forwarding filters origin metadata into a new response handle, so
63
+ // its streamed payload remains opaque even though origin is false.
64
+ // Pre-commit errors use ordinary non-streaming structured responses.
65
+ bodyClass: execution.response.origin === true
66
+ || (native.plan.capabilities.includes('request.body.forward') && execution.response.streaming === 1)
67
+ ? 'opaque' : 'structured'
68
+ }),
69
+ // The fixture ABI has hostcall traces, not canonical continuation telemetry.
70
+ effectCount: null,
71
+ continuations: Object.freeze([]),
72
+ resolutionOrder: Object.freeze([]),
73
+ evidence
74
+ });
75
+ };
76
+ }
77
+
78
+ module.exports = { prepareFastlyNativeExecution };