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

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 (69) hide show
  1. package/README.md +53 -1
  2. package/dist/config-schema.json +77 -0
  3. package/dist/index.d.ts +61 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +59 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +88 -5
  8. package/src/build/canonical-target.js +436 -0
  9. package/src/build/entities-native.js +564 -0
  10. package/src/build/fixed-memory-runtime.as.ts +148 -0
  11. package/src/build/kv-native.as.ts +229 -0
  12. package/src/build/kv-native.js +20 -0
  13. package/src/build/native-http-effects.js +871 -0
  14. package/src/build/native-http-shell.js +703 -0
  15. package/src/build/native-platform-capabilities.js +2196 -0
  16. package/src/build/s3-native.as.ts +215 -0
  17. package/src/build/s3-native.js +30 -0
  18. package/src/compiler/assets-package-out-parity-proof.js +338 -0
  19. package/src/compiler/assets-provider-proof.js +499 -0
  20. package/src/compiler/config-provider-resolution.js +573 -0
  21. package/src/compiler/fastly-adapter.js +564 -0
  22. package/src/compiler/fastly-command-entry.js +390 -0
  23. package/src/compiler/fastly-config-secret-serve.js +323 -0
  24. package/src/compiler/fastly-hostcall-binding.js +598 -0
  25. package/src/compiler/fastly-readiness.js +477 -0
  26. package/src/compiler/index.js +40 -0
  27. package/src/compiler/lifecycle-parity-contract.js +70 -0
  28. package/src/compiler/lifecycle-parity-proof.js +260 -0
  29. package/src/compiler/route-handler-effect-proof.js +255 -0
  30. package/src/compiler/runtime-provider-kv.js +237 -0
  31. package/src/compiler/runtime-provider-shape.js +109 -0
  32. package/src/config-api.js +63 -0
  33. package/src/config-schema.d.ts +13 -0
  34. package/src/config-schema.js +27 -0
  35. package/src/config-schema.json +77 -0
  36. package/src/index.js +3 -0
  37. package/src/index.ts +118 -0
  38. package/src/javascript/assets-adapter.js +122 -0
  39. package/src/javascript/bindings-adapter.js +237 -0
  40. package/src/javascript/errors.js +26 -0
  41. package/src/javascript/fetch-adapter.js +93 -0
  42. package/src/javascript/grip-broadcast.js +261 -0
  43. package/src/javascript/jwt-verifier.js +137 -0
  44. package/src/javascript/lifecycle.js +55 -0
  45. package/src/javascript/local-loader.js +138 -0
  46. package/src/javascript/logging.js +26 -0
  47. package/src/javascript/package-effects.js +68 -0
  48. package/src/javascript/runtime-host.js +108 -0
  49. package/src/javascript/sha256.js +128 -0
  50. package/src/javascript/source-package.js +776 -0
  51. package/src/javascript/support.js +142 -0
  52. package/src/javascript/target-support-policy.js +264 -0
  53. package/src/javascript/target.js +73 -0
  54. package/src/javascript/test-runtime.js +407 -0
  55. package/src/provider-contract.js +79 -0
  56. package/src/runtime/assets-provider.js +464 -0
  57. package/src/runtime/canonical-api-runtime.js +601 -0
  58. package/src/runtime/index.js +28 -0
  59. package/src/runtime/provider-primitives-contract.js +272 -0
  60. package/src/runtime/provider-primitives.js +82 -0
  61. package/src/runtime/route-handler-effects.js +507 -0
  62. package/src/testing/conditional-kv-host.js +109 -0
  63. package/src/testing/fastly-cli.js +538 -0
  64. package/src/testing/native-http-effects-host.js +390 -0
  65. package/src/testing/native-http-shell-host.js +190 -0
  66. package/src/testing/native-platform-capabilities-host.js +676 -0
  67. package/src/toolchain/config.js +131 -0
  68. package/src/toolchain/index.js +345 -0
  69. package/src/toolchain/s3.js +26 -0
@@ -0,0 +1,499 @@
1
+ 'use strict';
2
+
3
+ function loadAssetsContracts() {
4
+ try {
5
+ return require('@pulse-compute/wasm-contracts/assets/contracts');
6
+ } catch (error) {
7
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
8
+ return require('../../../../wasm/packages/contracts/src/assets/contracts.js');
9
+ }
10
+ throw error;
11
+ }
12
+ }
13
+
14
+ function loadDiagnostics() {
15
+ try {
16
+ return require('@pulse-compute/wasm-contracts/diagnostics');
17
+ } catch (error) {
18
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
19
+ return require('../../../../wasm/packages/contracts/src/diagnostics.js');
20
+ }
21
+ throw error;
22
+ }
23
+ }
24
+
25
+ const assetsContracts = loadAssetsContracts();
26
+ const {
27
+ PACKAGE_VERSION,
28
+ normalizeArtifact,
29
+ normalizeDiagnostic
30
+ } = loadDiagnostics();
31
+ const {
32
+ createFastlyAssetsProvider,
33
+ createDefaultStoresFromPlan,
34
+ buildFastlyAssetsProviderConfig,
35
+ normalizeHeaders
36
+ } = require('../runtime/assets-provider.js');
37
+
38
+ const phaseName = 'fastly-assets-provider-proof';
39
+
40
+ function clone(value) {
41
+ if (Array.isArray(value)) return value.map(clone);
42
+ if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clone(entry)]));
43
+ return value;
44
+ }
45
+
46
+ function normalizePlan(input) {
47
+ const plan = input && input.artifact ? input.artifact : input;
48
+ if (!plan || typeof plan !== 'object') return undefined;
49
+ return {
50
+ ...plan,
51
+ entries: Array.isArray(plan.entries) ? plan.entries.map(clone) : [],
52
+ payloadModes: plan.payloadModes ? clone(plan.payloadModes) : undefined
53
+ };
54
+ }
55
+
56
+ function plannedEntries(plan) {
57
+ return (plan && Array.isArray(plan.entries) ? plan.entries : [])
58
+ .filter((entry) => entry && entry.status !== 'unsupported' && entry.lookup && entry.lookup.store && entry.lookup.key)
59
+ .map(clone);
60
+ }
61
+
62
+ function normalizeKey(key) {
63
+ const text = String(key || '');
64
+ return text.startsWith('/') ? text : `/${text}`;
65
+ }
66
+
67
+ function makeDiagnostic(code, message, severity, details) {
68
+ return normalizeDiagnostic({
69
+ phase: phaseName,
70
+ severity: severity || 'error',
71
+ code,
72
+ message,
73
+ hint: details && details.hint,
74
+ details,
75
+ loc: { file: '<fastly-assets-provider-proof>' }
76
+ });
77
+ }
78
+
79
+ function headerValue(headers, name) {
80
+ const lower = String(name).toLowerCase();
81
+ const found = normalizeHeaders(headers).find(([key]) => String(key).toLowerCase() === lower);
82
+ return found ? found[1] : undefined;
83
+ }
84
+
85
+ function resultBody(result) {
86
+ if (!result) return undefined;
87
+ if (result.body !== undefined) return String(result.body);
88
+ if (result.bodyStream && Array.isArray(result.bodyStream.chunks)) {
89
+ return result.bodyStream.chunks.map((chunk) => Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk)).join('');
90
+ }
91
+ return undefined;
92
+ }
93
+
94
+ function check(name, passed, details, failures, diagnostics) {
95
+ const out = { name, status: passed ? 'ok' : 'error', details: details || {} };
96
+ if (!passed) {
97
+ failures.push(out);
98
+ diagnostics.push(makeDiagnostic(
99
+ assetsContracts.ASSETS_DIAGNOSTIC_CODES.FASTLY_PROVIDER_SMOKE_FAILED,
100
+ `Fastly assets provider proof check failed: ${name}.`,
101
+ 'error',
102
+ { check: name, details: out.details }
103
+ ));
104
+ }
105
+ return out;
106
+ }
107
+
108
+ function cloneEntryForPayload(entry, payloadMode, idSuffix) {
109
+ const next = clone(entry);
110
+ next.id = `${entry.id || 'asset'}-${idSuffix}`;
111
+ next.lookup = { ...(next.lookup || {}), payloadMode };
112
+ next.response = {
113
+ ...(next.response || {}),
114
+ lookupId: next.id,
115
+ payloadMode,
116
+ payloadStatus: payloadMode === 'binary-buffer' ? 'reserved-with-diagnostic' : 'lowering-plan-only'
117
+ };
118
+ return next;
119
+ }
120
+
121
+ function selectEntry(entries, method) {
122
+ const upper = String(method || 'GET').toUpperCase();
123
+ if (upper === 'HEAD') {
124
+ return entries.find((entry) => String(entry.lookup.method || 'GET').toUpperCase() === 'HEAD') || entries[0];
125
+ }
126
+ return entries.find((entry) => String(entry.lookup.method || 'GET').toUpperCase() === upper) || entries[0];
127
+ }
128
+
129
+ function bodyFixtureForEntry(entry, label) {
130
+ return `${label}:${entry.lookup.store}:${normalizeKey(entry.lookup.key)}`;
131
+ }
132
+
133
+ function storesWithBody(entry, body, options = {}) {
134
+ return {
135
+ [entry.lookup.store]: {
136
+ [normalizeKey(entry.lookup.key)]: {
137
+ status: options.status || (entry.response && entry.response.status) || 200,
138
+ body,
139
+ payloadMode: options.payloadMode,
140
+ cacheControl: entry.lookup.cacheControl,
141
+ headers: [
142
+ ['Content-Type', options.contentType || 'text/plain; charset=utf-8'],
143
+ ['X-PulseWasm-Fastly-Proof', options.label || '1']
144
+ ]
145
+ }
146
+ }
147
+ };
148
+ }
149
+
150
+ function sourceArtifactVersion(value) {
151
+ if (!value) return undefined;
152
+ if (value.artifact) return value.artifact.version;
153
+ return value.version;
154
+ }
155
+
156
+ function normalizeSourceArtifacts(inputs) {
157
+ const fastlyStreamBindingPlan = inputs.fastlyStreamBindingPlan || (inputs.fastlyHostcallBinding && inputs.fastlyHostcallBinding.streamBindingPlan);
158
+ const fastlyStreamHeaderAdapter = inputs.fastlyStreamHeaderAdapter || (inputs.fastlyAdapter && inputs.fastlyAdapter.streamHeaderAdapter);
159
+ return {
160
+ fastlyStreamBindingPlan: sourceArtifactVersion(fastlyStreamBindingPlan),
161
+ fastlyStreamHeaderAdapter: sourceArtifactVersion(fastlyStreamHeaderAdapter),
162
+ requestResultHeaders: sourceArtifactVersion(inputs.requestResultHeaders),
163
+ streamingPassthrough: sourceArtifactVersion(inputs.streamingPassthrough),
164
+ wasmHostAbi: sourceArtifactVersion(inputs.wasmHostAbi)
165
+ };
166
+ }
167
+
168
+ function buildFastlyAssetsProviderProof(inputs = {}) {
169
+ const cwd = inputs.cwd || process.cwd();
170
+ const generatedBy = inputs.generatedBy || PACKAGE_VERSION;
171
+ const plan = normalizePlan(inputs.assetsLoweringPlan || inputs.plan);
172
+ const diagnostics = [];
173
+ if (!plan) {
174
+ diagnostics.push(makeDiagnostic(
175
+ assetsContracts.ASSETS_DIAGNOSTIC_CODES.FASTLY_PROVIDER_PLAN_REQUIRED,
176
+ 'Fastly assets provider proof requires an assets-lowering-plan artifact.',
177
+ 'error',
178
+ { hint: 'Emit --assets-lowering-plan before requesting the Fastly assets provider proof.' }
179
+ ));
180
+ }
181
+
182
+ const entries = plannedEntries(plan);
183
+ if (plan && entries.length === 0) {
184
+ diagnostics.push(makeDiagnostic(
185
+ assetsContracts.ASSETS_DIAGNOSTIC_CODES.FASTLY_PROVIDER_PLAN_REQUIRED,
186
+ 'Fastly assets provider proof requires at least one static assets lookup/respond entry from the lowering plan.',
187
+ 'error',
188
+ { entries: 0, hint: 'Add assets.lookup("public", "/file.txt") and assets.respond(handle) to the entry source.' }
189
+ ));
190
+ }
191
+
192
+ const failures = [];
193
+ const checks = [];
194
+ const results = {};
195
+ const sourceArtifacts = normalizeSourceArtifacts(inputs);
196
+ let proofStores = inputs.stores;
197
+ const providerConfig = inputs.providerConfig || buildFastlyAssetsProviderConfig({ plan, resolvedConfig: inputs.resolvedConfig });
198
+ let provider;
199
+
200
+ checks.push(check(
201
+ 'Fastly assets provider config maps plan stores to symbolic config/secret refs',
202
+ providerConfig && providerConfig.provider === 'fastly' && providerConfig.symbolicRefsOnly === true && Array.isArray(providerConfig.stores) && providerConfig.stores.length === entries.length ? true : Boolean(providerConfig && providerConfig.stores && providerConfig.stores.length > 0 && providerConfig.configStore && providerConfig.secretStore),
203
+ {
204
+ stores: providerConfig && providerConfig.stores ? providerConfig.stores.length : 0,
205
+ configStore: providerConfig && providerConfig.configStore,
206
+ secretStore: providerConfig && providerConfig.secretStore,
207
+ symbolicRefsOnly: providerConfig && providerConfig.symbolicRefsOnly
208
+ },
209
+ failures,
210
+ diagnostics
211
+ ));
212
+
213
+ if (entries.length > 0) {
214
+ const getEntry = selectEntry(entries, 'GET');
215
+ const headEntry = selectEntry(entries, 'HEAD') || getEntry;
216
+ if (!proofStores) {
217
+ proofStores = createDefaultStoresFromPlan(plan);
218
+ proofStores[getEntry.lookup.store] = {
219
+ ...(proofStores[getEntry.lookup.store] || {}),
220
+ [normalizeKey(getEntry.lookup.key)]: {
221
+ status: (getEntry.response && getEntry.response.status) || 200,
222
+ body: bodyFixtureForEntry(getEntry, 'fastly-proof'),
223
+ cacheControl: getEntry.lookup.cacheControl,
224
+ headers: [['Content-Type', 'text/plain; charset=utf-8']]
225
+ }
226
+ };
227
+ proofStores[headEntry.lookup.store] = {
228
+ ...(proofStores[headEntry.lookup.store] || {}),
229
+ [normalizeKey(headEntry.lookup.key)]: {
230
+ status: (headEntry.response && headEntry.response.status) || 200,
231
+ body: bodyFixtureForEntry(headEntry, 'fastly-proof-head'),
232
+ cacheControl: headEntry.lookup.cacheControl,
233
+ headers: [['Content-Type', 'text/plain; charset=utf-8']]
234
+ }
235
+ };
236
+ }
237
+
238
+ provider = createFastlyAssetsProvider({
239
+ plan,
240
+ stores: proofStores,
241
+ mode: inputs.mode || 'provider-object-store',
242
+ providerConfig,
243
+ resolvedConfig: inputs.resolvedConfig,
244
+ sourceArtifacts
245
+ });
246
+ diagnostics.push(...(provider.diagnostics || []));
247
+
248
+ const getResult = provider.executeRequest({ method: 'GET', path: getEntry.lookup.key });
249
+ results.getText = {
250
+ status: getResult.status,
251
+ kind: getResult.kind,
252
+ body: resultBody(getResult),
253
+ headers: normalizeHeaders(getResult.headers),
254
+ diagnostics: getResult.diagnostics || []
255
+ };
256
+ checks.push(check(
257
+ 'GET maps to Fastly response path with status/headers/body',
258
+ getResult.status === ((getEntry.response && getEntry.response.status) || 200) && getResult.kind === 'fastly-response' && typeof getResult.body === 'string' && getResult.body.length > 0 && getResult.fastlyResponsePath === true,
259
+ { status: getResult.status, kind: getResult.kind, bodyLength: getResult.body ? String(getResult.body).length : 0, fastlyResponsePath: getResult.fastlyResponsePath },
260
+ failures,
261
+ diagnostics
262
+ ));
263
+ checks.push(check(
264
+ 'GET headers/status pass through into Fastly response shape',
265
+ Boolean(headerValue(getResult.headers, 'Content-Type')) && headerValue(getResult.headers, 'X-PulseWasm-Fastly-Asset-Key') === normalizeKey(getEntry.lookup.key),
266
+ { contentType: headerValue(getResult.headers, 'Content-Type'), assetKey: headerValue(getResult.headers, 'X-PulseWasm-Fastly-Asset-Key') },
267
+ failures,
268
+ diagnostics
269
+ ));
270
+ if (getEntry.lookup.cacheControl) {
271
+ checks.push(check(
272
+ 'GET cache-control policy is preserved in Fastly response headers when declared',
273
+ headerValue(getResult.headers, 'Cache-Control') === getEntry.lookup.cacheControl,
274
+ { expected: getEntry.lookup.cacheControl, actual: headerValue(getResult.headers, 'Cache-Control') },
275
+ failures,
276
+ diagnostics
277
+ ));
278
+ }
279
+
280
+ const headResult = provider.executeRequest({ method: 'HEAD', path: headEntry.lookup.key });
281
+ results.head = {
282
+ status: headResult.status,
283
+ kind: headResult.kind,
284
+ body: resultBody(headResult),
285
+ headers: normalizeHeaders(headResult.headers),
286
+ diagnostics: headResult.diagnostics || []
287
+ };
288
+ checks.push(check(
289
+ 'HEAD maps to Fastly response headers/status with no body',
290
+ headResult.status === ((headEntry.response && headEntry.response.status) || 200) && headResult.kind === 'fastly-empty' && headResult.body === undefined,
291
+ { status: headResult.status, kind: headResult.kind, body: headResult.body },
292
+ failures,
293
+ diagnostics
294
+ ));
295
+
296
+ const missingPath = '/__pulsewasm-fastly-missing-asset__';
297
+ const missingResult = provider.executeRequest({ method: 'GET', path: missingPath });
298
+ results.missing = {
299
+ status: missingResult.status,
300
+ kind: missingResult.kind,
301
+ passThroughOn404: missingResult.passThroughOn404,
302
+ diagnostics: missingResult.diagnostics || []
303
+ };
304
+ checks.push(check(
305
+ 'missing asset follows Fastly 404/pass-through policy',
306
+ missingResult.status === 404 && missingResult.kind === 'fastly-empty' && missingResult.passThroughOn404 === true,
307
+ { status: missingResult.status, kind: missingResult.kind, passThroughOn404: missingResult.passThroughOn404 },
308
+ failures,
309
+ diagnostics
310
+ ));
311
+
312
+ const methodResult = provider.executeRequest({ method: 'POST', path: getEntry.lookup.key });
313
+ results.unsupportedMethod = {
314
+ status: methodResult.status,
315
+ kind: methodResult.kind,
316
+ headers: normalizeHeaders(methodResult.headers),
317
+ diagnostics: methodResult.diagnostics || []
318
+ };
319
+ checks.push(check(
320
+ 'unsupported methods are diagnosed and rejected by Fastly proof',
321
+ methodResult.status === 405 && (methodResult.diagnostics || []).some((diag) => diag.code === assetsContracts.ASSETS_DIAGNOSTIC_CODES.UNSUPPORTED_METHOD),
322
+ { status: methodResult.status, diagnosticCodes: (methodResult.diagnostics || []).map((diag) => diag.code) },
323
+ failures,
324
+ diagnostics
325
+ ));
326
+
327
+ const streamEntry = cloneEntryForPayload(getEntry, 'stream-pass-through-response', 'stream');
328
+ const streamPlan = { ...plan, entries: [streamEntry] };
329
+ const streamProvider = createFastlyAssetsProvider({
330
+ plan: streamPlan,
331
+ stores: storesWithBody(streamEntry, bodyFixtureForEntry(streamEntry, 'fastly-proof-stream'), { payloadMode: 'stream-pass-through-response', label: 'stream' }),
332
+ providerConfig,
333
+ sourceArtifacts
334
+ });
335
+ const streamResult = streamProvider.executeRequest({ method: 'GET', path: streamEntry.lookup.key });
336
+ results.stream = {
337
+ status: streamResult.status,
338
+ kind: streamResult.kind,
339
+ fastlyStreamBindingPlanConnected: streamResult.fastlyStreamBindingPlanConnected,
340
+ responseRef: streamResult.responseRef,
341
+ streamRef: streamResult.streamRef,
342
+ body: resultBody(streamResult),
343
+ diagnostics: streamResult.diagnostics || []
344
+ };
345
+ checks.push(check(
346
+ 'stream pass-through response maps to Fastly stream binding plan path',
347
+ streamResult.status === 200 && streamResult.kind === 'fastly-stream' && streamResult.bodyStream && streamResult.bodyStream.hostOwnedBytes === true && resultBody(streamResult).length > 0,
348
+ { status: streamResult.status, kind: streamResult.kind, fastlyStreamBindingPlanConnected: streamResult.fastlyStreamBindingPlanConnected, responseRef: streamResult.responseRef, streamRef: streamResult.streamRef, bodyLength: resultBody(streamResult).length },
349
+ failures,
350
+ diagnostics
351
+ ));
352
+
353
+ const binaryEntry = cloneEntryForPayload(getEntry, 'binary-buffer', 'binary');
354
+ const binaryPlan = { ...plan, entries: [binaryEntry] };
355
+ const binaryProvider = createFastlyAssetsProvider({
356
+ plan: binaryPlan,
357
+ stores: storesWithBody(binaryEntry, Buffer.from('binary-proof'), { payloadMode: 'binary-buffer', label: 'binary' }),
358
+ providerConfig,
359
+ sourceArtifacts
360
+ });
361
+ const binaryResult = binaryProvider.executeRequest({ method: 'GET', path: binaryEntry.lookup.key });
362
+ results.binary = {
363
+ status: binaryResult.status,
364
+ kind: binaryResult.kind,
365
+ diagnostics: binaryResult.diagnostics || []
366
+ };
367
+ checks.push(check(
368
+ 'binary body/buffer mode remains reserved with diagnostic for Fastly',
369
+ binaryResult.status === 501 && (binaryResult.diagnostics || []).some((diag) => diag.code === assetsContracts.ASSETS_DIAGNOSTIC_CODES.BINARY_BODY_RESERVED),
370
+ { status: binaryResult.status, diagnosticCodes: (binaryResult.diagnostics || []).map((diag) => diag.code) },
371
+ failures,
372
+ diagnostics
373
+ ));
374
+ }
375
+
376
+ const errorDiagnostics = diagnostics.filter((entry) => (entry.severity || 'error') === 'error');
377
+ const artifact = normalizeArtifact({
378
+ version: assetsContracts.ASSETS_FASTLY_PROVIDER_PROOF_VERSION,
379
+ generatedBy,
380
+ phase: phaseName,
381
+ status: errorDiagnostics.length === 0 ? 'ok' : 'error',
382
+ provider: 'fastly',
383
+ providerPackage: '@pulse-compute/provider-fastly',
384
+ contractId: assetsContracts.ASSETS_CONTRACT_ID,
385
+ sourceAssetsLoweringPlanVersion: plan && plan.version,
386
+ loweringPlanConnected: Boolean(plan && entries.length > 0),
387
+ providerBehaviorImplemented: true,
388
+ compiledWasmLowering: false,
389
+ proofMode: 'direct-fastly-provider-smoke-connected-to-lowering-plan',
390
+ scope: {
391
+ mode: inputs.mode || 'provider-object-store',
392
+ methods: clone(assetsContracts.ASSETS_ALLOWED_METHODS),
393
+ staticStoreKeyFromLoweringPlan: true,
394
+ uploads: false,
395
+ invalidation: false,
396
+ mutationApis: false,
397
+ fullObjectStoreSemantics: false,
398
+ cacheControlPolicy: 'pass-through-when-declared',
399
+ notFoundPolicy: '404-pass-through',
400
+ configSecretRefs: 'symbolic-only'
401
+ },
402
+ providerConfig,
403
+ fastlyArtifacts: {
404
+ fastlyStreamBindingPlan: sourceArtifacts.fastlyStreamBindingPlan,
405
+ fastlyStreamHeaderAdapter: sourceArtifacts.fastlyStreamHeaderAdapter,
406
+ requestResultHeaders: sourceArtifacts.requestResultHeaders,
407
+ streamingPassthrough: sourceArtifacts.streamingPassthrough,
408
+ wasmHostAbi: sourceArtifacts.wasmHostAbi
409
+ },
410
+ payloadModes: {
411
+ textResponseBody: {
412
+ mode: 'text-response-body',
413
+ status: 'implemented-now',
414
+ proof: 'GET response body via Fastly-like response path'
415
+ },
416
+ headerPassThrough: {
417
+ mode: 'header-pass-through',
418
+ status: 'implemented-now',
419
+ proof: 'Content-Type, X-PulseWasm-Fastly-Asset-Key, and Cache-Control header checks'
420
+ },
421
+ statusResultPassThrough: {
422
+ mode: 'status-result-pass-through',
423
+ status: 'implemented-now',
424
+ proof: 'GET and HEAD status checks'
425
+ },
426
+ streamPassThroughResponse: {
427
+ mode: 'stream-pass-through-response',
428
+ contractStatus: assetsContracts.ASSETS_PAYLOAD_MODE_CLASSIFICATION.streamPassThroughResponse.status,
429
+ planStatus: plan && plan.payloadModes && plan.payloadModes.stream ? plan.payloadModes.stream.status : undefined,
430
+ hostSurfaceStatus: assetsContracts.ASSETS_PAYLOAD_MODE_CLASSIFICATION.streamPassThroughResponse.hostSurfaceStatus,
431
+ status: entries.length > 0 ? 'implemented-fastly-proof' : 'not-exercised',
432
+ providerWired: entries.length > 0,
433
+ fastlyStreamBindingPlanConnected: Boolean(sourceArtifacts.fastlyStreamBindingPlan),
434
+ binaryStaticPayloadDecision: 'stream-pass-through-for-stream-mode; binary-buffer-reserved',
435
+ proof: 'direct Fastly provider returns kind=fastly-stream with responseRef/streamRef-style binding metadata'
436
+ },
437
+ binaryBodyBufferResponse: {
438
+ mode: 'binary-buffer',
439
+ contractStatus: assetsContracts.ASSETS_PAYLOAD_MODE_CLASSIFICATION.binaryBodyBufferResponse.status,
440
+ planStatus: plan && plan.payloadModes && plan.payloadModes.binary ? plan.payloadModes.binary.status : undefined,
441
+ status: 'reserved-with-diagnostic',
442
+ providerWired: false,
443
+ diagnosticCode: assetsContracts.ASSETS_DIAGNOSTIC_CODES.BINARY_BODY_RESERVED
444
+ }
445
+ },
446
+ entriesConsumed: entries.map((entry) => ({
447
+ id: entry.id,
448
+ store: entry.lookup.store,
449
+ key: entry.lookup.key,
450
+ method: entry.lookup.method || 'GET',
451
+ payloadMode: (entry.response && entry.response.payloadMode) || entry.lookup.payloadMode || 'text-response-body',
452
+ response: entry.response ? { status: entry.response.status, payloadMode: entry.response.payloadMode } : undefined
453
+ })),
454
+ smoke: {
455
+ checks,
456
+ failedChecks: failures,
457
+ results
458
+ },
459
+ policy: {
460
+ compilerOwnsProviderBehavior: false,
461
+ providerOwnsImplementation: true,
462
+ planBuilderOwner: plan && plan.policy && plan.policy.builderOwner ? plan.policy.builderOwner : '@pulse-compute/assets',
463
+ providerOwner: '@pulse-compute/provider-fastly',
464
+ consumesLoweringPlan: true,
465
+ fastlyBehaviorInCompiler: false,
466
+ officialSdkDependency: false
467
+ },
468
+ summary: {
469
+ entries: entries.length,
470
+ checks: checks.length,
471
+ failedChecks: failures.length,
472
+ providerConfigMapped: checks.some((entry) => entry.name.startsWith('Fastly assets provider config') && entry.status === 'ok'),
473
+ getTextValidated: checks.some((entry) => entry.name.startsWith('GET maps') && entry.status === 'ok'),
474
+ headValidated: checks.some((entry) => entry.name.startsWith('HEAD maps') && entry.status === 'ok'),
475
+ notFoundValidated: checks.some((entry) => entry.name.startsWith('missing asset') && entry.status === 'ok'),
476
+ unsupportedMethodValidated: checks.some((entry) => entry.name.startsWith('unsupported methods') && entry.status === 'ok'),
477
+ streamValidated: checks.some((entry) => entry.name.startsWith('stream pass-through') && entry.status === 'ok'),
478
+ streamBindingPlanConnected: Boolean(sourceArtifacts.fastlyStreamBindingPlan),
479
+ binaryReservedValidated: checks.some((entry) => entry.name.startsWith('binary body') && entry.status === 'ok'),
480
+ diagnostics: diagnostics.length,
481
+ errorDiagnostics: errorDiagnostics.length
482
+ },
483
+ diagnostics
484
+ }, cwd);
485
+
486
+ return {
487
+ artifact,
488
+ diagnostics,
489
+ entries: artifact.entriesConsumed,
490
+ smoke: artifact.smoke,
491
+ providerConfig: artifact.providerConfig,
492
+ hasErrors: errorDiagnostics.length > 0
493
+ };
494
+ }
495
+
496
+ module.exports = {
497
+ buildFastlyAssetsProviderProof,
498
+ phaseName
499
+ };