@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.
- package/README.md +53 -1
- package/dist/config-schema.json +77 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/package.json +88 -5
- package/src/build/canonical-target.js +436 -0
- package/src/build/entities-native.js +564 -0
- package/src/build/fixed-memory-runtime.as.ts +148 -0
- package/src/build/kv-native.as.ts +229 -0
- package/src/build/kv-native.js +20 -0
- package/src/build/native-http-effects.js +871 -0
- package/src/build/native-http-shell.js +703 -0
- package/src/build/native-platform-capabilities.js +2196 -0
- package/src/build/s3-native.as.ts +215 -0
- package/src/build/s3-native.js +30 -0
- package/src/compiler/assets-package-out-parity-proof.js +338 -0
- package/src/compiler/assets-provider-proof.js +499 -0
- package/src/compiler/config-provider-resolution.js +573 -0
- package/src/compiler/fastly-adapter.js +564 -0
- package/src/compiler/fastly-command-entry.js +390 -0
- package/src/compiler/fastly-config-secret-serve.js +323 -0
- package/src/compiler/fastly-hostcall-binding.js +598 -0
- package/src/compiler/fastly-readiness.js +477 -0
- package/src/compiler/index.js +40 -0
- package/src/compiler/lifecycle-parity-contract.js +70 -0
- package/src/compiler/lifecycle-parity-proof.js +260 -0
- package/src/compiler/route-handler-effect-proof.js +255 -0
- package/src/compiler/runtime-provider-kv.js +237 -0
- package/src/compiler/runtime-provider-shape.js +109 -0
- package/src/config-api.js +63 -0
- package/src/config-schema.d.ts +13 -0
- package/src/config-schema.js +27 -0
- package/src/config-schema.json +77 -0
- package/src/index.js +3 -0
- package/src/index.ts +118 -0
- package/src/javascript/assets-adapter.js +122 -0
- package/src/javascript/bindings-adapter.js +237 -0
- package/src/javascript/errors.js +26 -0
- package/src/javascript/fetch-adapter.js +93 -0
- package/src/javascript/grip-broadcast.js +261 -0
- package/src/javascript/jwt-verifier.js +137 -0
- package/src/javascript/lifecycle.js +55 -0
- package/src/javascript/local-loader.js +138 -0
- package/src/javascript/logging.js +26 -0
- package/src/javascript/package-effects.js +68 -0
- package/src/javascript/runtime-host.js +108 -0
- package/src/javascript/sha256.js +128 -0
- package/src/javascript/source-package.js +776 -0
- package/src/javascript/support.js +142 -0
- package/src/javascript/target-support-policy.js +264 -0
- package/src/javascript/target.js +73 -0
- package/src/javascript/test-runtime.js +407 -0
- package/src/provider-contract.js +79 -0
- package/src/runtime/assets-provider.js +464 -0
- package/src/runtime/canonical-api-runtime.js +601 -0
- package/src/runtime/index.js +28 -0
- package/src/runtime/provider-primitives-contract.js +272 -0
- package/src/runtime/provider-primitives.js +82 -0
- package/src/runtime/route-handler-effects.js +507 -0
- package/src/testing/conditional-kv-host.js +109 -0
- package/src/testing/fastly-cli.js +538 -0
- package/src/testing/native-http-effects-host.js +390 -0
- package/src/testing/native-http-shell-host.js +190 -0
- package/src/testing/native-platform-capabilities-host.js +676 -0
- package/src/toolchain/config.js +131 -0
- package/src/toolchain/index.js +345 -0
- package/src/toolchain/s3.js +26 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = loadContractsDiagnostics();
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
function loadContractsDiagnostics() {
|
|
8
|
+
try {
|
|
9
|
+
return require('@pulse-compute/wasm-contracts/diagnostics');
|
|
10
|
+
} catch (error) {
|
|
11
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
12
|
+
return require('../../../../wasm/packages/contracts/src/diagnostics.js');
|
|
13
|
+
}
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const PHASE = '13C-pre';
|
|
19
|
+
const FASTLY_HOSTCALL_BINDING_CONTRACT_VERSION = 'pulsewasm.fastly-hostcall-binding-contract.v1';
|
|
20
|
+
const FASTLY_HOSTCALL_MODULES_VERSION = 'pulsewasm.fastly-hostcall-modules.v1';
|
|
21
|
+
const FASTLY_REF_LIFECYCLE_VERSION = 'pulsewasm.fastly-ref-lifecycle.v1';
|
|
22
|
+
const FASTLY_STREAM_BINDING_PLAN_VERSION = 'pulsewasm.fastly-stream-binding-plan.v1';
|
|
23
|
+
const FASTLY_PACK_INPUTS_VERSION = 'pulsewasm.fastly-pack-inputs.v1';
|
|
24
|
+
const FASTLY_BINDING_RISK_REPORT_VERSION = 'pulsewasm.fastly-binding-risk-report.v1';
|
|
25
|
+
|
|
26
|
+
function sha256Text(text) {
|
|
27
|
+
return crypto.createHash('sha256').update(String(text || ''), 'utf8').digest('hex');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function makeFile(file, text) {
|
|
31
|
+
return { file, text, bytes: Buffer.byteLength(text, 'utf8'), sha256: sha256Text(text) };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function diag(code, message, hint, details) {
|
|
35
|
+
return normalizeDiagnostic({
|
|
36
|
+
phase: 'fastly-hostcall-binding',
|
|
37
|
+
severity: 'error',
|
|
38
|
+
code,
|
|
39
|
+
message,
|
|
40
|
+
hint,
|
|
41
|
+
details,
|
|
42
|
+
loc: { file: '<fastly-hostcall-binding>' }
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function source(url, title, notes) {
|
|
47
|
+
return { url, title, notes };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildHostcallModules() {
|
|
51
|
+
const requiredFirstProof = [
|
|
52
|
+
{
|
|
53
|
+
module: 'fastly_http_req',
|
|
54
|
+
status: 'required-first-proof',
|
|
55
|
+
pulseSurface: ['HostRequest', 'incoming method/path/header extraction', 'request handoff'],
|
|
56
|
+
notes: 'Fastly custom SDK documentation calls out fastly_http_req as required for basic request handling.'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
module: 'fastly_http_body',
|
|
60
|
+
status: 'required-first-proof',
|
|
61
|
+
pulseSurface: ['RESULT_STREAM', 'response/body handle creation', 'host-owned byte movement'],
|
|
62
|
+
notes: 'Fastly custom SDK documentation calls out fastly_http_body as required for responding to incoming requests.'
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const candidateLater = [
|
|
67
|
+
{
|
|
68
|
+
module: 'backend/dynamic backend hostcall surface',
|
|
69
|
+
status: 'candidate-later',
|
|
70
|
+
pulseSurface: ['backend-capabilities.json', 'ctx.fetch backend-fetch effect'],
|
|
71
|
+
blockedBy: 'effect runtime implementation',
|
|
72
|
+
notes: 'Backend fetch remains behind declared ctx.fetch effects; do not add arbitrary backend access in the adapter.'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
module: 'config store surface',
|
|
76
|
+
status: 'candidate-later',
|
|
77
|
+
pulseSurface: ['profile runtime config', 'runtime.capabilities'],
|
|
78
|
+
blockedBy: 'profile-to-store mapping proof'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
module: 'secret store surface',
|
|
82
|
+
status: 'candidate-later',
|
|
83
|
+
pulseSurface: ['env secret refs', 'backend auth headers'],
|
|
84
|
+
blockedBy: 'secret mapping proof; secrets must not be compiled into Wasm'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
module: 'Fanout / WebSockets passthrough surface',
|
|
88
|
+
status: 'candidate-later',
|
|
89
|
+
pulseSurface: ['channel-broadcaster.json', 'future pulse.grip package contract'],
|
|
90
|
+
blockedBy: 'real pulse.grip package analysis and sidecar contract'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
module: 'logging surface',
|
|
94
|
+
status: 'candidate-later',
|
|
95
|
+
pulseSurface: ['diagnostics/logging host capability'],
|
|
96
|
+
blockedBy: 'logging capability contract'
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
module: 'clock/timer provider',
|
|
100
|
+
status: 'candidate-later',
|
|
101
|
+
pulseSurface: ['effect-timeout-policy.json', 'timer/sleep effects'],
|
|
102
|
+
blockedBy: 'effect runtime proof and provider binding'
|
|
103
|
+
}
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
version: FASTLY_HOSTCALL_MODULES_VERSION,
|
|
108
|
+
generatedBy: PACKAGE_VERSION,
|
|
109
|
+
phase: PHASE,
|
|
110
|
+
posture: {
|
|
111
|
+
minimalFirstProof: true,
|
|
112
|
+
officialSdkDependency: false,
|
|
113
|
+
customHostcallBinding: true,
|
|
114
|
+
adapterImplementation: false
|
|
115
|
+
},
|
|
116
|
+
requiredFirstProof,
|
|
117
|
+
candidateLater,
|
|
118
|
+
unavailableOrDeferred: [
|
|
119
|
+
{
|
|
120
|
+
module: 'AssemblyScript Fastly SDK',
|
|
121
|
+
status: 'do-not-use',
|
|
122
|
+
reason: 'Deprecated/unsupported; use SDKs only as references and bind PulseWasm contract directly later.'
|
|
123
|
+
}
|
|
124
|
+
],
|
|
125
|
+
summary: {
|
|
126
|
+
requiredFirstProofModules: requiredFirstProof.length,
|
|
127
|
+
candidateLaterModules: candidateLater.length,
|
|
128
|
+
adapterImplemented: false,
|
|
129
|
+
readyForStreamHeaderProof: true
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildRefLifecycle() {
|
|
135
|
+
const refs = [
|
|
136
|
+
{
|
|
137
|
+
ref: 'FastlyRequestRef',
|
|
138
|
+
owner: 'Fastly host/runtime',
|
|
139
|
+
createdBy: 'incoming Compute request hostcall surface',
|
|
140
|
+
consumedBy: 'PulseWasm adapter request normalization',
|
|
141
|
+
validUntil: 'active request processing completes',
|
|
142
|
+
pulseMapping: 'HostRequest -> method/path/ordered headers/body handle seed',
|
|
143
|
+
rules: [
|
|
144
|
+
'do not persist beyond active request',
|
|
145
|
+
'do not expose to user handlers',
|
|
146
|
+
'adapter translates into PulseWasm HostRequest facts'
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
ref: 'FastlyBodyRef',
|
|
151
|
+
owner: 'Fastly host/runtime',
|
|
152
|
+
createdBy: 'incoming request body or outgoing response body hostcall surface',
|
|
153
|
+
consumedBy: 'stream/pass-through adapter',
|
|
154
|
+
validUntil: 'active request/result lifetime',
|
|
155
|
+
pulseMapping: 'HostStreamRef / streamRef / responseRef payload body',
|
|
156
|
+
rules: [
|
|
157
|
+
'host owns stream bytes',
|
|
158
|
+
'PulseWasm owns opaque refs only',
|
|
159
|
+
'do not copy bytes into Wasm unless explicitly requested by a future body-read/binary path'
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
ref: 'FastlyResponseRef',
|
|
164
|
+
owner: 'Fastly host/runtime',
|
|
165
|
+
createdBy: 'adapter response construction or backend response passthrough',
|
|
166
|
+
consumedBy: 'PulseWasm RESULT_STREAM/responseRef bridge',
|
|
167
|
+
validUntil: 'response is returned/sent',
|
|
168
|
+
pulseMapping: 'HostResult(kind="stream", responseRef, streamRef, headers)',
|
|
169
|
+
rules: [
|
|
170
|
+
'response headers may be merged with Pulse response mutations',
|
|
171
|
+
'backend/platform response headers are preserved unless deleted/overridden',
|
|
172
|
+
'ordered/repeated headers must remain observable'
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
ref: 'PulseCtxRef',
|
|
177
|
+
owner: 'PulseWasm runtime',
|
|
178
|
+
createdBy: 'PulseWasm request execution',
|
|
179
|
+
consumedBy: 'compiled handlers and adapter capability calls',
|
|
180
|
+
validUntil: 'active dispatch/effect context lifetime',
|
|
181
|
+
pulseMapping: 'existing ctx ABI',
|
|
182
|
+
rules: [
|
|
183
|
+
'do not store Fastly refs directly in user-visible ctx',
|
|
184
|
+
'context generations guard future effect resumes',
|
|
185
|
+
'setup failures throw; Pulse routing outcomes return HostResult'
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
version: FASTLY_REF_LIFECYCLE_VERSION,
|
|
192
|
+
generatedBy: PACKAGE_VERSION,
|
|
193
|
+
phase: PHASE,
|
|
194
|
+
posture: {
|
|
195
|
+
hostOwnsPlatformHandles: true,
|
|
196
|
+
wasmOwnsOpaqueRefsOnly: true,
|
|
197
|
+
activeRequestLifetime: true,
|
|
198
|
+
noLongLivedBorrowedPointers: true,
|
|
199
|
+
adapterImplementation: false
|
|
200
|
+
},
|
|
201
|
+
refs,
|
|
202
|
+
cleanupPolicy: {
|
|
203
|
+
firstProof: 'active-request lifetime only',
|
|
204
|
+
streamFailureBeforeStart: 'return normal Pulse error/result',
|
|
205
|
+
streamFailureAfterStart: 'host/platform transport failure policy',
|
|
206
|
+
clientDisconnect: 'host cancels stream/body handles',
|
|
207
|
+
bodyReadEffect: 'reserved'
|
|
208
|
+
},
|
|
209
|
+
summary: {
|
|
210
|
+
refs: refs.length,
|
|
211
|
+
streamBytesOwnedByHost: true,
|
|
212
|
+
pulseWasmCopiesBytesByDefault: false,
|
|
213
|
+
adapterImplemented: false
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function buildStreamBindingPlan() {
|
|
219
|
+
const steps = [
|
|
220
|
+
{
|
|
221
|
+
order: 0,
|
|
222
|
+
name: 'incoming-request-normalization',
|
|
223
|
+
pulseSurface: 'HostRequest',
|
|
224
|
+
fastlySurface: 'request hostcall handle',
|
|
225
|
+
status: 'contracted-not-implemented'
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
order: 1,
|
|
229
|
+
name: 'ordered-header-extraction',
|
|
230
|
+
pulseSurface: 'ordered repeated header bag',
|
|
231
|
+
fastlySurface: 'request/response header hostcall surface',
|
|
232
|
+
status: 'critical-first-proof'
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
order: 2,
|
|
236
|
+
name: 'compiled-runtime-execution',
|
|
237
|
+
pulseSurface: 'compiled-wasm runtime',
|
|
238
|
+
fastlySurface: 'custom Wasm binary packaged for Compute',
|
|
239
|
+
status: 'platform-neutral-ready'
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
order: 3,
|
|
243
|
+
name: 'result-stream-translation',
|
|
244
|
+
pulseSurface: 'RESULT_STREAM / responseRef / streamRef',
|
|
245
|
+
fastlySurface: 'response/body handle',
|
|
246
|
+
status: 'critical-first-proof'
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
order: 4,
|
|
250
|
+
name: 'header-merge-application',
|
|
251
|
+
pulseSurface: 'delete/set/append ordered response mutations',
|
|
252
|
+
fastlySurface: 'response header mutation hostcalls',
|
|
253
|
+
status: 'critical-first-proof'
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
order: 5,
|
|
257
|
+
name: 'body-return',
|
|
258
|
+
pulseSurface: 'host-owned stream bytes',
|
|
259
|
+
fastlySurface: 'Compute response body',
|
|
260
|
+
status: 'critical-first-proof'
|
|
261
|
+
}
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
version: FASTLY_STREAM_BINDING_PLAN_VERSION,
|
|
266
|
+
generatedBy: PACKAGE_VERSION,
|
|
267
|
+
phase: PHASE,
|
|
268
|
+
posture: {
|
|
269
|
+
firstAdapterProofShouldBeStreamHeaderOnly: true,
|
|
270
|
+
backendFetchBlockedByEffectRuntime: true,
|
|
271
|
+
pulseGripBlockedByPackageAnalysis: true,
|
|
272
|
+
pulseAssetsBlockedByPackageAnalysis: true
|
|
273
|
+
},
|
|
274
|
+
headerMergePolicy: {
|
|
275
|
+
preserveBackendHeaders: true,
|
|
276
|
+
deleteCaseInsensitive: true,
|
|
277
|
+
setOverridesExistingName: true,
|
|
278
|
+
appendPreservesRepeatedHeaders: true,
|
|
279
|
+
preserveGripChannelRepeats: true
|
|
280
|
+
},
|
|
281
|
+
streamPolicy: {
|
|
282
|
+
hostOwnedBytes: true,
|
|
283
|
+
wasmOwnsBytes: false,
|
|
284
|
+
resultKind: 'RESULT_STREAM',
|
|
285
|
+
resultBinaryReserved: true,
|
|
286
|
+
responseRefPassthrough: true,
|
|
287
|
+
streamRefPassthrough: true,
|
|
288
|
+
requestBodyParsingTouched: false
|
|
289
|
+
},
|
|
290
|
+
steps,
|
|
291
|
+
proofCases: [
|
|
292
|
+
'GET/POST request maps into HostRequest facts',
|
|
293
|
+
'repeated request headers survive extraction',
|
|
294
|
+
'PulseWasm RESULT_STREAM maps to Fastly response/body handle',
|
|
295
|
+
'backend/platform response headers are preserved',
|
|
296
|
+
'Pulse response header delete/set/append mutates pass-through headers deterministically',
|
|
297
|
+
'Grip-Channel repeated headers survive the response mapping'
|
|
298
|
+
],
|
|
299
|
+
summary: {
|
|
300
|
+
steps: steps.length,
|
|
301
|
+
criticalProofSteps: steps.filter((step) => /critical/.test(step.status)).length,
|
|
302
|
+
streamPassthroughReadyForProof: true,
|
|
303
|
+
adapterImplemented: false
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function buildPackInputs(inputs = {}) {
|
|
309
|
+
return {
|
|
310
|
+
version: FASTLY_PACK_INPUTS_VERSION,
|
|
311
|
+
generatedBy: PACKAGE_VERSION,
|
|
312
|
+
phase: PHASE,
|
|
313
|
+
posture: {
|
|
314
|
+
packagingContractOnly: true,
|
|
315
|
+
packCommandCandidate: 'fastly compute pack --wasm-binary <compiled-pulsewasm.wasm>',
|
|
316
|
+
fastlyTomlGeneration: 'deferred',
|
|
317
|
+
viceroyExecution: 'deferred',
|
|
318
|
+
deployCommand: 'deferred'
|
|
319
|
+
},
|
|
320
|
+
requiredInputs: [
|
|
321
|
+
{
|
|
322
|
+
name: 'compiledPulseWasmBinary',
|
|
323
|
+
source: 'generated/compiled-wasm-runtime/pulsewasm-compiled-runtime.wasm',
|
|
324
|
+
status: inputs.compiledWasmRuntime ? 'available-from-prior-phase' : 'required'
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: 'fastlyManifest',
|
|
328
|
+
source: 'fastly.toml / Compute manifest inputs',
|
|
329
|
+
status: 'deferred-contract-needed'
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: 'hostcallBindingLayer',
|
|
333
|
+
source: 'future generated Fastly binding shim',
|
|
334
|
+
status: 'not-implemented'
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'deploymentPosture',
|
|
338
|
+
source: 'deployment-posture.json',
|
|
339
|
+
status: inputs.deploymentPosture ? 'available' : 'required'
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: 'hostCapabilities',
|
|
343
|
+
source: 'host-capabilities.json',
|
|
344
|
+
status: inputs.hostCapabilities ? 'available' : 'required'
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'fastlyCapabilityMap',
|
|
348
|
+
source: 'fastly-capability-map.json',
|
|
349
|
+
status: inputs.fastlyReadiness?.capabilityMap ? 'available' : 'required'
|
|
350
|
+
}
|
|
351
|
+
],
|
|
352
|
+
explicitNonInputs: [
|
|
353
|
+
'Fastly official SDK dependency',
|
|
354
|
+
'deprecated AssemblyScript SDK',
|
|
355
|
+
'real pulse.grip implementation',
|
|
356
|
+
'real pulse.assets implementation',
|
|
357
|
+
'ctx.fetch effect runtime'
|
|
358
|
+
],
|
|
359
|
+
summary: {
|
|
360
|
+
requiredInputs: 6,
|
|
361
|
+
availableInputs: [inputs.compiledWasmRuntime, inputs.deploymentPosture, inputs.hostCapabilities, inputs.fastlyReadiness?.capabilityMap].filter(Boolean).length,
|
|
362
|
+
implementationReady: false,
|
|
363
|
+
adapterPackagingReady: false
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function buildBindingRiskReport() {
|
|
369
|
+
const risks = [
|
|
370
|
+
{
|
|
371
|
+
code: 'FASTLY_CUSTOM_HOSTCALL_UNSUPPORTED_BY_VENDOR',
|
|
372
|
+
severity: 'high',
|
|
373
|
+
status: 'accepted-risk-for-audit',
|
|
374
|
+
message: 'Custom SDK/hostcall paths are possible but not supported by Fastly; adapter work must be isolated and testable.'
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
code: 'FASTLY_HOSTCALL_SIGNATURE_DRIFT',
|
|
378
|
+
severity: 'high',
|
|
379
|
+
status: 'must-track',
|
|
380
|
+
message: 'Binding layer must track Viceroy/Fastly hostcall interface changes and avoid baking assumptions into PulseWasm core.'
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
code: 'FASTLY_STREAM_LIFECYCLE_RISK',
|
|
384
|
+
severity: 'high',
|
|
385
|
+
status: 'critical-first-proof',
|
|
386
|
+
message: 'Stream/body handles require explicit lifetime and cleanup behavior; first adapter proof must focus on stream/header mapping.'
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
code: 'FASTLY_HEADER_REPEAT_COLLAPSE_RISK',
|
|
390
|
+
severity: 'medium',
|
|
391
|
+
status: 'must-test',
|
|
392
|
+
message: 'Repeated headers such as Grip-Channel must not collapse during request/response mapping.'
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
code: 'FASTLY_BACKEND_FETCH_EFFECT_BLOCKED',
|
|
396
|
+
severity: 'medium',
|
|
397
|
+
status: 'blocked',
|
|
398
|
+
message: 'Backend fetch mapping waits for PulseWasm effect runtime and declared backend capability execution.'
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
code: 'FASTLY_FANOUT_GRIP_BLOCKED',
|
|
402
|
+
severity: 'medium',
|
|
403
|
+
status: 'blocked',
|
|
404
|
+
message: 'Fanout/GRIP waits for real pulse.grip package contract and sidecar analysis.'
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
code: 'FASTLY_DEBUG_ARTIFACT_STRIP_REQUIRED',
|
|
408
|
+
severity: 'medium',
|
|
409
|
+
status: 'future-optimization',
|
|
410
|
+
message: 'Production package must strip debug/proof artifacts and avoid shipping WAT/diagnostic bulk.'
|
|
411
|
+
}
|
|
412
|
+
];
|
|
413
|
+
return {
|
|
414
|
+
version: FASTLY_BINDING_RISK_REPORT_VERSION,
|
|
415
|
+
generatedBy: PACKAGE_VERSION,
|
|
416
|
+
phase: PHASE,
|
|
417
|
+
risks,
|
|
418
|
+
mitigations: [
|
|
419
|
+
'Keep 13C-pre contract-only.',
|
|
420
|
+
'Implement first Fastly proof as stream/header mapping only.',
|
|
421
|
+
'Use SDKs and Viceroy hostcall definitions as references, not dependencies.',
|
|
422
|
+
'Keep platform behavior out of PulseWasm core semantics.',
|
|
423
|
+
'Gate backend fetch and Fanout behind existing Pulse contracts.'
|
|
424
|
+
],
|
|
425
|
+
summary: {
|
|
426
|
+
risks: risks.length,
|
|
427
|
+
high: risks.filter((risk) => risk.severity === 'high').length,
|
|
428
|
+
blocked: risks.filter((risk) => risk.status === 'blocked').length,
|
|
429
|
+
adapterImplementationAllowed: false
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function buildBindingContract({ hostcallModules, refLifecycle, streamBindingPlan, packInputs, riskReport }, inputs = {}) {
|
|
435
|
+
const sources = [
|
|
436
|
+
source('https://www.fastly.com/documentation/guides/compute/developer-guides/custom/', 'Unofficial SDKs on the Compute platform', 'Custom SDKs are possible because Compute is WASI-powered, but unsupported; hostcalls live in Viceroy .witx definitions and basic functionality requires fastly_http_req and fastly_http_body.'),
|
|
437
|
+
source('https://www.fastly.com/documentation/reference/cli/compute/pack/', 'fastly compute pack', 'Fastly CLI can package a pre-compiled Wasm binary using --wasm-binary.'),
|
|
438
|
+
source('https://www.fastly.com/documentation/reference/compute/sdks/', 'Compute language SDKs', 'Official SDKs are references only for PulseWasm: Rust, JavaScript, and Go.'),
|
|
439
|
+
source('https://github.com/fastly/Viceroy', 'Viceroy', 'Local testing server and Rust library used through fastly compute serve; useful reference for hostcall behavior and custom SDK testing.' )
|
|
440
|
+
];
|
|
441
|
+
const readiness = inputs.fastlyReadiness?.artifact?.verdict || {};
|
|
442
|
+
return {
|
|
443
|
+
version: FASTLY_HOSTCALL_BINDING_CONTRACT_VERSION,
|
|
444
|
+
generatedBy: PACKAGE_VERSION,
|
|
445
|
+
phase: PHASE,
|
|
446
|
+
posture: {
|
|
447
|
+
contractOnly: true,
|
|
448
|
+
adapterImplemented: false,
|
|
449
|
+
deploymentPackageGenerated: false,
|
|
450
|
+
fastlyTomlGenerated: false,
|
|
451
|
+
viceroyExecution: false,
|
|
452
|
+
sdkDependency: false,
|
|
453
|
+
sdkReference: true,
|
|
454
|
+
fastlyDefinesPulseSemantics: false,
|
|
455
|
+
pulseWasmMapsExistingRuntimeContract: true
|
|
456
|
+
},
|
|
457
|
+
bindingStrategy: {
|
|
458
|
+
selected: 'custom-hostcall-binding-after-contract',
|
|
459
|
+
immediateNextProof: 'Fastly stream/header adapter proof',
|
|
460
|
+
sdkWrapperRejectedForCore: true,
|
|
461
|
+
rustSdkPrimaryReference: true,
|
|
462
|
+
jsGoSdksCrossCheck: true,
|
|
463
|
+
assemblyScriptSdkRevival: false
|
|
464
|
+
},
|
|
465
|
+
currentReadiness: {
|
|
466
|
+
from13B: readiness,
|
|
467
|
+
compiledWasmRuntimeAvailable: Boolean(inputs.compiledWasmRuntime),
|
|
468
|
+
streamPrimitiveAvailable: Boolean(inputs.streamingPassthrough || inputs.compiledWasmRuntime),
|
|
469
|
+
hostCapabilitiesAvailable: Boolean(inputs.hostCapabilities),
|
|
470
|
+
adapterReady: false
|
|
471
|
+
},
|
|
472
|
+
modules: {
|
|
473
|
+
artifact: 'fastly-hostcall-modules.json',
|
|
474
|
+
requiredFirstProof: hostcallModules.requiredFirstProof.map((module) => module.module),
|
|
475
|
+
candidateLater: hostcallModules.candidateLater.map((module) => module.module)
|
|
476
|
+
},
|
|
477
|
+
lifecycle: {
|
|
478
|
+
artifact: 'fastly-ref-lifecycle.json',
|
|
479
|
+
streamBytesOwnedByHost: refLifecycle.summary.streamBytesOwnedByHost,
|
|
480
|
+
activeRequestLifetimeOnly: true
|
|
481
|
+
},
|
|
482
|
+
streamBinding: {
|
|
483
|
+
artifact: 'fastly-stream-binding-plan.json',
|
|
484
|
+
critical: true,
|
|
485
|
+
proofCases: streamBindingPlan.proofCases
|
|
486
|
+
},
|
|
487
|
+
packaging: {
|
|
488
|
+
artifact: 'fastly-pack-inputs.json',
|
|
489
|
+
packCommandCandidate: packInputs.posture.packCommandCandidate,
|
|
490
|
+
implementationReady: false
|
|
491
|
+
},
|
|
492
|
+
riskReport: {
|
|
493
|
+
artifact: 'fastly-binding-risk-report.json',
|
|
494
|
+
highRisks: riskReport.summary.high,
|
|
495
|
+
blocked: riskReport.summary.blocked
|
|
496
|
+
},
|
|
497
|
+
sources,
|
|
498
|
+
verdict: {
|
|
499
|
+
readyForHostcallBindingContract: true,
|
|
500
|
+
readyForFastlyStreamHeaderProof: true,
|
|
501
|
+
readyForFastlyAdapterImplementation: false,
|
|
502
|
+
readyForFastlyPackaging: false,
|
|
503
|
+
reason: 'The PulseWasm runtime contract is ready to be mapped to Fastly hostcalls, but 13C-pre intentionally stops before adapter/packaging implementation.'
|
|
504
|
+
},
|
|
505
|
+
summary: {
|
|
506
|
+
requiredFirstProofModules: hostcallModules.summary.requiredFirstProofModules,
|
|
507
|
+
candidateLaterModules: hostcallModules.summary.candidateLaterModules,
|
|
508
|
+
refsTracked: refLifecycle.summary.refs,
|
|
509
|
+
streamProofCases: streamBindingPlan.proofCases.length,
|
|
510
|
+
packInputs: packInputs.summary.requiredInputs,
|
|
511
|
+
risks: riskReport.summary.risks,
|
|
512
|
+
adapterImplemented: false,
|
|
513
|
+
readyForFastlyStreamHeaderProof: true,
|
|
514
|
+
readyForFastlyAdapter: false,
|
|
515
|
+
diagnostics: 0
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function markdown({ artifact, hostcallModules, refLifecycle, streamBindingPlan, packInputs, riskReport }) {
|
|
521
|
+
const lines = [];
|
|
522
|
+
lines.push('# Fastly Hostcall Binding Contract');
|
|
523
|
+
lines.push('');
|
|
524
|
+
lines.push(`Generated by ${artifact.generatedBy}.`);
|
|
525
|
+
lines.push('');
|
|
526
|
+
lines.push('## Status');
|
|
527
|
+
lines.push('');
|
|
528
|
+
lines.push('Contract only. No Fastly adapter, package, `fastly.toml`, WIT generation, Viceroy execution, or deployment is implemented in this phase.');
|
|
529
|
+
lines.push('');
|
|
530
|
+
lines.push('## Binding posture');
|
|
531
|
+
lines.push('');
|
|
532
|
+
lines.push('- Fastly SDKs remain references, not dependencies.');
|
|
533
|
+
lines.push('- PulseWasm maps its existing runtime/host ABI onto Fastly hostcalls later.');
|
|
534
|
+
lines.push('- Fastly does not define PulseWasm core semantics.');
|
|
535
|
+
lines.push('- The first adapter proof should focus on headers and stream/pass-through.');
|
|
536
|
+
lines.push('');
|
|
537
|
+
lines.push('## Required first-proof modules');
|
|
538
|
+
lines.push('');
|
|
539
|
+
for (const module of hostcallModules.requiredFirstProof) {
|
|
540
|
+
lines.push(`- ${module.module}: ${module.notes}`);
|
|
541
|
+
}
|
|
542
|
+
lines.push('');
|
|
543
|
+
lines.push('## Ref lifecycle');
|
|
544
|
+
lines.push('');
|
|
545
|
+
for (const ref of refLifecycle.refs) {
|
|
546
|
+
lines.push(`- ${ref.ref}: owner=${ref.owner}; validUntil=${ref.validUntil}`);
|
|
547
|
+
}
|
|
548
|
+
lines.push('');
|
|
549
|
+
lines.push('## Stream/header proof cases');
|
|
550
|
+
lines.push('');
|
|
551
|
+
for (const proof of streamBindingPlan.proofCases) lines.push(`- ${proof}`);
|
|
552
|
+
lines.push('');
|
|
553
|
+
lines.push('## Packaging inputs');
|
|
554
|
+
lines.push('');
|
|
555
|
+
for (const input of packInputs.requiredInputs) lines.push(`- ${input.name}: ${input.status}`);
|
|
556
|
+
lines.push('');
|
|
557
|
+
lines.push('## Risks');
|
|
558
|
+
lines.push('');
|
|
559
|
+
for (const risk of riskReport.risks) lines.push(`- ${risk.code} (${risk.severity}): ${risk.message}`);
|
|
560
|
+
lines.push('');
|
|
561
|
+
lines.push('## Verdict');
|
|
562
|
+
lines.push('');
|
|
563
|
+
lines.push(`Ready for binding contract: ${artifact.verdict.readyForHostcallBindingContract}`);
|
|
564
|
+
lines.push(`Ready for stream/header proof: ${artifact.verdict.readyForFastlyStreamHeaderProof}`);
|
|
565
|
+
lines.push(`Ready for adapter implementation: ${artifact.verdict.readyForFastlyAdapterImplementation}`);
|
|
566
|
+
lines.push('');
|
|
567
|
+
return `${lines.join('\n')}\n`;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function buildFastlyHostcallBinding(inputs = {}, options = {}) {
|
|
571
|
+
const diagnostics = [];
|
|
572
|
+
if (!inputs.fastlyReadiness) {
|
|
573
|
+
diagnostics.push(diag(
|
|
574
|
+
'PULSEWASM_FASTLY_HOSTCALL_REQUIRES_READINESS_AUDIT',
|
|
575
|
+
'Fastly hostcall binding contract requires the 13B Fastly readiness audit artifact.',
|
|
576
|
+
'Run with --emit-fastly-readiness before --emit-fastly-hostcall-binding.'
|
|
577
|
+
));
|
|
578
|
+
}
|
|
579
|
+
const hostcallModules = buildHostcallModules();
|
|
580
|
+
const refLifecycle = buildRefLifecycle();
|
|
581
|
+
const streamBindingPlan = buildStreamBindingPlan();
|
|
582
|
+
const packInputs = buildPackInputs(inputs);
|
|
583
|
+
const riskReport = buildBindingRiskReport();
|
|
584
|
+
const artifact = normalizeArtifact(buildBindingContract({ hostcallModules, refLifecycle, streamBindingPlan, packInputs, riskReport }, inputs), options.cwd || process.cwd());
|
|
585
|
+
const files = [makeFile('generated/host/fastly-hostcall-binding-contract.md', markdown({ artifact, hostcallModules, refLifecycle, streamBindingPlan, packInputs, riskReport }))];
|
|
586
|
+
return {
|
|
587
|
+
artifact,
|
|
588
|
+
hostcallModules: normalizeArtifact(hostcallModules, options.cwd || process.cwd()),
|
|
589
|
+
refLifecycle: normalizeArtifact(refLifecycle, options.cwd || process.cwd()),
|
|
590
|
+
streamBindingPlan: normalizeArtifact(streamBindingPlan, options.cwd || process.cwd()),
|
|
591
|
+
packInputs: normalizeArtifact(packInputs, options.cwd || process.cwd()),
|
|
592
|
+
riskReport: normalizeArtifact(riskReport, options.cwd || process.cwd()),
|
|
593
|
+
files,
|
|
594
|
+
diagnostics
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
module.exports = { buildFastlyHostcallBinding };
|