@pulse-compute/provider-node 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 (42) hide show
  1. package/README.md +61 -1
  2. package/package.json +62 -6
  3. package/src/capabilities.js +65 -0
  4. package/src/compiler/assets-provider-proof.js +434 -0
  5. package/src/compiler/backend-json-request-body-proof.js +90 -0
  6. package/src/compiler/index.js +38 -0
  7. package/src/compiler/node-adapter.js +452 -0
  8. package/src/compiler/node-compiled-wasm-assets-lifecycle-proof.js +435 -0
  9. package/src/compiler/request-json-body-proof.js +181 -0
  10. package/src/compiler/route-handler-compiled-wasm-bridge.js +323 -0
  11. package/src/compiler/route-handler-effect-proof.js +215 -0
  12. package/src/compiler/route-handler-live-origin-proof.js +372 -0
  13. package/src/compiler/schema-decode-result-proof.js +180 -0
  14. package/src/compiler/schema-response-codec-proof.js +149 -0
  15. package/src/config/s3.js +20 -0
  16. package/src/events/reference-adapter.js +145 -0
  17. package/src/index.js +57 -0
  18. package/src/javascript/assets-adapter.js +68 -0
  19. package/src/javascript/bindings-adapter.js +90 -0
  20. package/src/javascript/fetch-adapter.js +187 -0
  21. package/src/javascript/grip-broadcast.js +243 -0
  22. package/src/javascript/jwt-verifier.js +137 -0
  23. package/src/javascript/lifecycle.js +69 -0
  24. package/src/javascript/node-adapter.js +284 -0
  25. package/src/javascript/package-effects.js +84 -0
  26. package/src/javascript/runtime-host.js +170 -0
  27. package/src/javascript/s3.js +21 -0
  28. package/src/javascript/source-package.js +439 -0
  29. package/src/javascript/support.js +205 -0
  30. package/src/javascript/target-support-policy.js +136 -0
  31. package/src/javascript/target.js +37 -0
  32. package/src/javascript/test-runtime.js +112 -0
  33. package/src/native/target.js +78 -0
  34. package/src/runtime/assets-provider.js +776 -0
  35. package/src/runtime/canonical-api-runtime.js +604 -0
  36. package/src/runtime/compiled-wasm-node-adapter-kv.js +15 -0
  37. package/src/runtime/conditional-kv.js +93 -0
  38. package/src/runtime/index.js +18 -0
  39. package/src/runtime/jwt-verifier.js +264 -0
  40. package/src/runtime/route-handler-effects.js +757 -0
  41. package/src/runtime/s3-reader.js +131 -0
  42. package/src/toolchain.js +269 -0
@@ -0,0 +1,372 @@
1
+ 'use strict';
2
+
3
+ function loadHandlerEffectContracts() {
4
+ try {
5
+ return require('@pulse-compute/wasm-contracts/handler/effects');
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/handler/effects.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 handlerEffects = loadHandlerEffectContracts();
26
+ const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = loadDiagnostics();
27
+ const {
28
+ createNodeLiveOriginRouteHandlerEffectProvider,
29
+ normalizeLiveOriginsFromResolvedConfig,
30
+ headerValue
31
+ } = require('../runtime/route-handler-effects.js');
32
+
33
+ const phaseName = 'node-live-origin-route-handler-effect-proof';
34
+
35
+ function clone(value) {
36
+ if (Array.isArray(value)) return value.map(clone);
37
+ if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clone(entry)]));
38
+ return value;
39
+ }
40
+
41
+ function normalizePlan(input) {
42
+ const plan = input && input.artifact && typeof input.artifact === 'object' ? input.artifact : input;
43
+ if (!plan || typeof plan !== 'object') return undefined;
44
+ return {
45
+ ...plan,
46
+ routes: Array.isArray(plan.routes) ? plan.routes.map(clone) : [],
47
+ continuations: Array.isArray(plan.continuations) ? plan.continuations.map(clone) : []
48
+ };
49
+ }
50
+
51
+ function plannedResolveEntries(plan) {
52
+ const entries = [];
53
+ for (const route of plan && plan.routes || []) {
54
+ for (const resolve of route.resolves || []) {
55
+ if (resolve && Array.isArray(resolve.effects) && resolve.effects.length > 0) entries.push({ route, resolve });
56
+ }
57
+ }
58
+ return entries;
59
+ }
60
+
61
+ function firstEntryByShape(entries, shape) {
62
+ return entries.find((entry) => entry.resolve && entry.resolve.groupShape === shape);
63
+ }
64
+
65
+ function makeDiagnostic(code, message, severity, details) {
66
+ return normalizeDiagnostic({
67
+ phase: phaseName,
68
+ severity: severity || 'error',
69
+ code,
70
+ message,
71
+ hint: details && details.hint,
72
+ details,
73
+ loc: { file: '<node-live-origin-route-handler-effect-proof>' }
74
+ });
75
+ }
76
+
77
+ const VOLATILE_HEADERS = new Set(['date', 'connection', 'keep-alive', 'transfer-encoding']);
78
+
79
+ function stableHeaders(headers) {
80
+ const entries = Array.isArray(headers) ? headers : Object.entries(headers || {});
81
+ return entries
82
+ .map(([name, value]) => [String(name).toLowerCase(), String(value)])
83
+ .filter(([name]) => !VOLATILE_HEADERS.has(name))
84
+ .sort(([left], [right]) => left.localeCompare(right));
85
+ }
86
+
87
+ function compactResponse(response) {
88
+ if (!response || typeof response !== 'object') return response;
89
+ return {
90
+ status: response.status,
91
+ ok: response.ok,
92
+ kind: response.kind,
93
+ method: response.request && response.request.method,
94
+ path: response.request && response.request.path,
95
+ headers: stableHeaders(response.headers),
96
+ bodyPreview: response.body === undefined ? undefined : String(response.body).slice(0, 120)
97
+ };
98
+ }
99
+
100
+ function compactFinalResult(result) {
101
+ if (!result || typeof result !== 'object') return result;
102
+ return {
103
+ status: result.status,
104
+ kind: result.kind,
105
+ headers: stableHeaders(result.headers),
106
+ bodyPreview: result.body === undefined ? undefined : String(result.body).slice(0, 160)
107
+ };
108
+ }
109
+
110
+ function sanitizeProofValue(value) {
111
+ if (Array.isArray(value)) return value.map(sanitizeProofValue);
112
+ if (!value || typeof value !== 'object') return value;
113
+ if (Array.isArray(value.headers)) return compactResponse(value);
114
+ if (value.finalResult) return { ...value, finalResult: compactFinalResult(value.finalResult) };
115
+ const out = {};
116
+ for (const [key, entry] of Object.entries(value)) {
117
+ if (key === 'headers') out.headers = stableHeaders(entry);
118
+ else if (key === 'head') out.head = compactResponse(entry);
119
+ else if (key === 'finalResult') out.finalResult = compactFinalResult(entry);
120
+ else out[key] = sanitizeProofValue(entry);
121
+ }
122
+ return out;
123
+ }
124
+
125
+ function check(name, condition, details, failures, diagnostics) {
126
+ const proofDetails = sanitizeProofValue(details || {});
127
+ const entry = { name, status: condition ? 'ok' : 'error', details: proofDetails };
128
+ if (!condition) {
129
+ failures.push(entry);
130
+ diagnostics.push(makeDiagnostic(handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeEffectSmokeFailed, `Node live-origin route-handler effect proof check failed: ${name}.`, 'error', { check: name, details: proofDetails }));
131
+ }
132
+ return entry;
133
+ }
134
+
135
+ function cloneEffectWithMethod(effect, method) {
136
+ const next = clone(effect);
137
+ next.request = { ...(next.request || {}), method };
138
+ return next;
139
+ }
140
+
141
+ function serializeExecution(execution) {
142
+ if (!execution) return undefined;
143
+ return {
144
+ status: execution.status,
145
+ routeId: execution.routeId,
146
+ route: execution.route && {
147
+ routeId: execution.route.routeId,
148
+ method: execution.route.method,
149
+ path: execution.route.path,
150
+ handlerName: execution.route.handlerName
151
+ },
152
+ groupShape: execution.groupShape,
153
+ continuation: execution.continuation,
154
+ continuationPlanned: execution.continuationPlanned,
155
+ effectCount: execution.effectCount,
156
+ effects: (execution.effects || []).map((effect) => ({
157
+ name: effect.name || '$default',
158
+ backend: effect.backend,
159
+ request: effect.request && { method: effect.request.method, path: effect.request.path },
160
+ response: compactResponse(effect.response)
161
+ })),
162
+ resolved: Object.fromEntries(Object.entries(execution.resolved || {}).map(([name, response]) => [name, compactResponse(response)])),
163
+ finalResult: compactFinalResult(execution.finalResult),
164
+ responseHandleSurface: clone(execution.responseHandleSurface),
165
+ continuationResultAccesses: clone(execution.continuationResultAccesses),
166
+ continuationResponseMethodCalls: clone(execution.continuationResponseMethodCalls),
167
+ lifecycle: clone(execution.lifecycle),
168
+ diagnostics: (execution.diagnostics || []).map((entry) => ({ code: entry.code, severity: entry.severity }))
169
+ };
170
+ }
171
+
172
+ function defaultReferenceContinuations() {
173
+ return {
174
+ renderUser(ctx) {
175
+ const user = ctx.resolved();
176
+ if (!user.ok()) {
177
+ return ctx.result.jsonText(user.status(), user.text(), {
178
+ headers: {
179
+ 'content-type': user.header('content-type'),
180
+ 'x-reference-continuation': 'renderUser'
181
+ }
182
+ });
183
+ }
184
+ return ctx.result.jsonText(200, user.text(), {
185
+ headers: {
186
+ 'content-type': user.header('content-type'),
187
+ 'x-reference-continuation': 'renderUser'
188
+ }
189
+ });
190
+ },
191
+ renderDashboard(ctx) {
192
+ const user = ctx.resolved('user');
193
+ const posts = ctx.resolved('posts');
194
+ const count = ctx.resolved('count');
195
+ if (!user.ok()) {
196
+ return ctx.result.jsonText(user.status(), user.text(), {
197
+ headers: {
198
+ 'content-type': user.header('content-type'),
199
+ 'x-reference-continuation': 'renderDashboard'
200
+ }
201
+ });
202
+ }
203
+ let body;
204
+ try {
205
+ body = JSON.stringify({
206
+ user: JSON.parse(user.jsonText()),
207
+ posts: JSON.parse(posts.jsonText()),
208
+ postCount: count.header('x-count')
209
+ });
210
+ } catch (_) {
211
+ body = user.text() + posts.text();
212
+ }
213
+ return ctx.result.jsonText(200, body, {
214
+ headers: {
215
+ 'content-type': 'application/json; charset=utf-8',
216
+ 'x-reference-continuation': 'renderDashboard'
217
+ }
218
+ });
219
+ }
220
+ };
221
+ }
222
+
223
+ function responseJsonBody(execution) {
224
+ try {
225
+ return JSON.parse(execution && execution.finalResult && execution.finalResult.body || '{}');
226
+ } catch (_) {
227
+ return {};
228
+ }
229
+ }
230
+
231
+ function normalizeOriginSummary(provider) {
232
+ return (provider.originSummary || []).map((entry) => ({ backend: entry.backend, baseUrl: entry.baseUrl }));
233
+ }
234
+
235
+ async function buildNodeLiveOriginRouteHandlerEffectProof(inputs = {}) {
236
+ const cwd = inputs.cwd || process.cwd();
237
+ const generatedBy = inputs.generatedBy || PACKAGE_VERSION;
238
+ const plan = normalizePlan(inputs.routeHandlerEffectPlan || inputs.plan);
239
+ const diagnostics = [];
240
+ const failures = [];
241
+ const checks = [];
242
+
243
+ if (!plan) {
244
+ diagnostics.push(makeDiagnostic(handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeEffectPlanRequired, 'Node live-origin route-handler effect proof requires a route-handler-effect-plan artifact.', 'error', { hint: 'Emit route-handler-effect-plan.json before requesting the live-origin Node proof.' }));
245
+ }
246
+
247
+ const entries = plannedResolveEntries(plan);
248
+ if (plan && entries.length === 0) {
249
+ diagnostics.push(makeDiagnostic(handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeEffectNoPlannedEffects, 'Node live-origin route-handler effect proof requires at least one planned ctx.resolve(ctx.fetch(...), continuation) boundary.', 'error', { routes: Array.isArray(plan.routes) ? plan.routes.length : 0 }));
250
+ }
251
+
252
+ const origins = { ...normalizeLiveOriginsFromResolvedConfig(inputs.resolvedConfig), ...(inputs.origins || inputs.liveOrigins || {}) };
253
+ const continuations = inputs.continuations || defaultReferenceContinuations();
254
+ const provider = createNodeLiveOriginRouteHandlerEffectProvider({
255
+ plan,
256
+ origins,
257
+ continuations,
258
+ defaultParams: inputs.defaultParams || {},
259
+ timeoutMs: inputs.timeoutMs || 2000,
260
+ redactOriginUrls: inputs.redactOriginUrls !== false,
261
+ mode: inputs.mode || 'live-origin-http'
262
+ });
263
+ diagnostics.push(...(provider.diagnostics || []));
264
+
265
+ if (Object.keys(origins || {}).length === 0) {
266
+ diagnostics.push(makeDiagnostic(handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeLiveOriginRequired, 'Node live-origin route-handler effect proof requires at least one backend origin base URL.', 'error', { hint: 'Configure runtime.capabilities.backends.<name>.baseUrl or pass liveOrigins to the proof builder.' }));
267
+ }
268
+
269
+ let singleExecution;
270
+ let missingExecution;
271
+ let namedExecution;
272
+ let unsupportedResult;
273
+ let missingBackendResult;
274
+
275
+ if (entries.length > 0) {
276
+ const single = firstEntryByShape(entries, 'single') || entries[0];
277
+ singleExecution = await provider.executeResolve(single.route, single.resolve, inputs.params || { id: 'abc' });
278
+ const singleResolved = singleExecution.resolved && (singleExecution.resolved.$default || singleExecution.resolved[Object.keys(singleExecution.resolved || {})[0]]);
279
+ checks.push(check('single ctx.resolve(ctx.fetch(...), continuation) performs a live origin GET', singleExecution.status === 'resolved' && Boolean(singleResolved) && singleResolved.status >= 200 && singleResolved.status < 300 && Boolean(singleResolved.body), { status: singleExecution.status, resolvedStatus: singleResolved && singleResolved.status, bodyPreview: singleResolved && String(singleResolved.body).slice(0, 80) }, failures, diagnostics));
280
+ checks.push(check('live origin response headers are visible through ctx.resolved().header(...)', Boolean(singleResolved) && Boolean(headerValue(singleResolved.headers, 'content-type')), { headers: singleResolved && singleResolved.headers }, failures, diagnostics));
281
+ checks.push(check('continuation re-enters with live origin resolved handle', singleExecution.continuationPlanned === true && singleExecution.lifecycle && singleExecution.lifecycle.liveOriginNetworkFetch === true, { continuation: singleExecution.continuation, lifecycle: singleExecution.lifecycle }, failures, diagnostics));
282
+
283
+ missingExecution = await provider.executeResolve(single.route, single.resolve, inputs.missingParams || { id: 'missing' });
284
+ checks.push(check('live origin 404 still completes the explicit continuation lifecycle', missingExecution.status === 'resolved' && missingExecution.finalResult && missingExecution.finalResult.status === 404, { status: missingExecution.status, finalResult: missingExecution.finalResult }, failures, diagnostics));
285
+
286
+ const named = firstEntryByShape(entries, 'named-object');
287
+ if (named) {
288
+ namedExecution = await provider.executeResolve(named.route, named.resolve, inputs.params || { id: 'abc' });
289
+ const names = Object.keys(namedExecution.resolved || {}).sort();
290
+ const head = Object.values(namedExecution.resolved || {}).find((entry) => entry.request && entry.request.method === 'HEAD');
291
+ const namedBody = responseJsonBody(namedExecution);
292
+ checks.push(check('named ctx.resolve({ name: ctx.fetch(...) }, continuation) performs all live origin fetches', namedExecution.status === 'resolved' && names.includes('user') && names.includes('posts') && names.includes('count'), { status: namedExecution.status, names }, failures, diagnostics));
293
+ checks.push(check('live origin HEAD effect resolves status/headers with empty body', Boolean(head) && head.status >= 200 && head.status < 300 && head.body === '' && Boolean(headerValue(head.headers, 'x-count')), { head }, failures, diagnostics));
294
+ checks.push(check('named continuation can compose live JSON/text origin responses', namedExecution.finalResult && namedExecution.finalResult.status === 200 && namedBody.user && namedBody.posts && String(namedBody.postCount || '') !== '', { finalResult: namedExecution.finalResult }, failures, diagnostics));
295
+ } else {
296
+ checks.push(check('named object effect group is present in live-origin proof fixture', false, { entries: entries.map((entry) => ({ routeId: entry.route.routeId, groupShape: entry.resolve.groupShape })) }, failures, diagnostics));
297
+ }
298
+
299
+ const firstEffect = single.resolve.effects[0];
300
+ unsupportedResult = (await provider.executeFetch(cloneEffectWithMethod(firstEffect, 'PUT'), { route: single.route, params: { id: 'abc' } })).toJSON();
301
+ checks.push(check('unsupported backend fetch methods are diagnosed before network fetch', unsupportedResult.status === 405 && unsupportedResult.diagnostics.some((entry) => entry.code === handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeEffectUnsupportedMethod), { status: unsupportedResult.status, diagnostics: unsupportedResult.diagnostics.map((entry) => entry.code) }, failures, diagnostics));
302
+
303
+ const missingEffect = clone(firstEffect);
304
+ missingEffect.backend = 'missingBackend';
305
+ missingBackendResult = (await provider.executeFetch(missingEffect, { route: single.route, params: { id: 'abc' } })).toJSON();
306
+ checks.push(check('missing live-origin backend is diagnosed', missingBackendResult.status === 502 && missingBackendResult.diagnostics.some((entry) => entry.code === handlerEffects.ROUTE_HANDLER_EFFECT_DIAGNOSTICS.nodeEffectBackendMissing), { status: missingBackendResult.status, diagnostics: missingBackendResult.diagnostics.map((entry) => entry.code) }, failures, diagnostics));
307
+ }
308
+
309
+ const errorDiagnostics = diagnostics.filter((entry) => (entry.severity || 'error') === 'error');
310
+ const policy = handlerEffects.defaultRouteHandlerNodeLiveOriginEffectProofPolicy();
311
+ const effects = entries.flatMap((entry) => entry.resolve.effects || []);
312
+ const artifact = normalizeArtifact({
313
+ version: handlerEffects.ROUTE_HANDLER_NODE_LIVE_ORIGIN_EFFECT_PROOF_VERSION,
314
+ generatedBy,
315
+ phase: handlerEffects.ROUTE_HANDLER_NODE_LIVE_ORIGIN_EFFECT_PROOF_PHASE,
316
+ artifact: handlerEffects.ROUTE_HANDLER_NODE_LIVE_ORIGIN_EFFECT_PROOF_ARTIFACT,
317
+ contractId: handlerEffects.ROUTE_HANDLER_EFFECT_CONTRACT_ID,
318
+ provider: 'node',
319
+ status: errorDiagnostics.length === 0 ? 'ok' : 'error',
320
+ routeEffectPlanConnected: Boolean(plan),
321
+ loweringPlanConnected: Boolean(plan),
322
+ sourcePlanVersion: plan && plan.version,
323
+ sourcePlanArtifact: plan && plan.artifact,
324
+ providerBehaviorImplemented: true,
325
+ nodeProviderEffectExecutionImplemented: true,
326
+ nodeProviderLiveOriginFetchImplemented: true,
327
+ compiledWasmEffectExecutionImplemented: false,
328
+ compiledWasmSuspendResumeBridgeImplemented: false,
329
+ scope: policy,
330
+ runtime: { version: handlerEffects.ROUTE_HANDLER_NODE_LIVE_ORIGIN_EFFECT_RUNTIME_VERSION, package: '@pulse-compute/provider-node/runtime/route-handler-effects' },
331
+ proof: { package: '@pulse-compute/provider-node/compiler/route-handler-live-origin-proof', mode: inputs.mode || 'live-origin-http', singleEffect: true, namedObjectEffectGroup: true, arrayEffectGroup: false, fixtureBackends: false, actualNetworkFetch: true, bootstrapOrigin: true },
332
+ lifecycle: { ...handlerEffects.ROUTE_HANDLER_EFFECT_LIFECYCLE, nodeProofHostEffectExecution: entries.length > 0, nodeProofContinuationReentryPlanned: entries.length > 0, nodeProofLiveOriginNetworkFetch: entries.length > 0, languageLevelAsync: false, promises: false, asyncAwait: false, asyncify: false },
333
+ methods: [...handlerEffects.ROUTE_HANDLER_EFFECT_ALLOWED_METHODS],
334
+ groupShapes: clone(handlerEffects.ROUTE_HANDLER_EFFECT_GROUP_SHAPES),
335
+ responseHandleSurface: [...handlerEffects.ROUTE_HANDLER_CONTEXT_SURFACE.resolvedFetchResponse],
336
+ reservedResponseHandleSurface: [...handlerEffects.ROUTE_HANDLER_CONTEXT_SURFACE.reservedResolvedFetchResponse],
337
+ policy: { compilerOwnsProviderBehavior: false, compilerOrchestrationOnly: true, providerOwner: '@pulse-compute/provider-node', directProviderSmoke: true, localFixtureFetch: false, actualNetworkFetch: true, liveOriginFetch: true, userAuthoredAsync: false, languageLevelAsync: false, promises: false, asyncAwait: false, asyncify: false, compiledWasmSuspendResumeBridge: false },
338
+ origins: normalizeOriginSummary(provider),
339
+ entriesConsumed: entries.map(({ route, resolve }) => ({ routeId: route.routeId, method: route.method, path: route.path, handlerName: route.handlerName, groupShape: resolve.groupShape, continuation: resolve.continuation && resolve.continuation.name, effects: (resolve.effects || []).map((effect) => ({ name: effect.name || '$default', kind: effect.kind, backend: effect.backend, method: effect.request && effect.request.method })) })),
340
+ smoke: { checks, failedChecks: failures, results: { single: serializeExecution(singleExecution), missingOriginResponse: serializeExecution(missingExecution), namedObject: serializeExecution(namedExecution), unsupportedMethod: unsupportedResult, missingBackend: missingBackendResult } },
341
+ summary: {
342
+ routesWithEffects: entries.length,
343
+ effects: effects.length,
344
+ backendFetchEffects: effects.filter((effect) => effect.kind === 'backend-fetch').length,
345
+ checks: checks.length,
346
+ failedChecks: failures.length,
347
+ singleResolveValidated: checks.some((entry) => entry.name.startsWith('single ctx.resolve') && entry.status === 'ok'),
348
+ singleValidated: checks.some((entry) => entry.name.startsWith('single ctx.resolve') && entry.status === 'ok'),
349
+ namedObjectValidated: checks.some((entry) => entry.name.startsWith('named ctx.resolve') && entry.status === 'ok'),
350
+ liveOriginNetworkFetchValidated: checks.some((entry) => entry.name.includes('live origin GET') && entry.status === 'ok'),
351
+ liveOrigin404Validated: checks.some((entry) => entry.name.startsWith('live origin 404') && entry.status === 'ok'),
352
+ headValidated: checks.some((entry) => entry.name.startsWith('live origin HEAD') && entry.status === 'ok'),
353
+ continuationReentryValidated: checks.some((entry) => entry.name.includes('Continuation') || entry.name.includes('continuation') && entry.status === 'ok'),
354
+ unsupportedMethodValidated: checks.some((entry) => entry.name.startsWith('unsupported backend fetch methods') && entry.status === 'ok'),
355
+ unsupportedMethodDiagnosticValidated: checks.some((entry) => entry.name.startsWith('unsupported backend fetch methods') && entry.status === 'ok'),
356
+ missingBackendValidated: checks.some((entry) => entry.name.startsWith('missing live-origin backend') && entry.status === 'ok'),
357
+ missingBackendDiagnosticValidated: checks.some((entry) => entry.name.startsWith('missing live-origin backend') && entry.status === 'ok'),
358
+ responseHandleSurfaceValidated: checks.some((entry) => entry.name.includes('headers are visible') && entry.status === 'ok'),
359
+ providerBehaviorImplemented: true,
360
+ actualNetworkFetch: true,
361
+ fixtureBackends: false,
362
+ compiledWasmEffectExecutionImplemented: false,
363
+ diagnostics: diagnostics.length,
364
+ errorDiagnostics: errorDiagnostics.length,
365
+ errors: errorDiagnostics.length
366
+ },
367
+ diagnostics
368
+ }, cwd);
369
+ return { artifact, diagnostics, failures, provider };
370
+ }
371
+
372
+ module.exports = { buildNodeLiveOriginRouteHandlerEffectProof, phaseName };
@@ -0,0 +1,180 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ function loadSchemaDecodeContracts() {
7
+ const local = path.resolve(__dirname, '../../../../wasm/packages/contracts/src/handler/schema-decode-result.js');
8
+ if (fs.existsSync(local)) return require(local);
9
+ return require('@pulse-compute/wasm-contracts/handler/schema-decode-result');
10
+ }
11
+ function loadDiagnostics() {
12
+ const local = path.resolve(__dirname, '../../../../wasm/packages/contracts/src/diagnostics.js');
13
+ if (fs.existsSync(local)) return require(local);
14
+ return require('@pulse-compute/wasm-contracts/diagnostics');
15
+ }
16
+ function loadArtifactsDir() {
17
+ const local = path.resolve(__dirname, '../../../../wasm/packages/build-support/src/artifacts-dir.js');
18
+ if (fs.existsSync(local)) return require(local);
19
+ return require('@pulse-compute/wasm-build-support/artifacts-dir');
20
+ }
21
+ const schemaDecode = loadSchemaDecodeContracts();
22
+ const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = loadDiagnostics();
23
+ const { getDefaultArtifactsDir } = loadArtifactsDir();
24
+ const { createCompiledHandlerBridgeRuntime, findCompiledHandlerWasm } = require('./route-handler-compiled-wasm-bridge.js');
25
+
26
+ const phaseName = 'node-schema-decode-result-proof';
27
+
28
+ function clone(value) {
29
+ if (Array.isArray(value)) return value.map(clone);
30
+ if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clone(entry)]));
31
+ return value;
32
+ }
33
+ function normalizePlan(input) {
34
+ const plan = input && input.artifact && typeof input.artifact === 'object' ? input.artifact : input;
35
+ if (!plan || typeof plan !== 'object') return undefined;
36
+ return { ...plan, routes: Array.isArray(plan.routes) ? plan.routes.map(clone) : [] };
37
+ }
38
+ function schemaRoutes(plan) {
39
+ return (Array.isArray(plan && plan.routes) ? plan.routes : []).filter((route) => route && Array.isArray(route.decodes) && route.decodes.length > 0);
40
+ }
41
+ function makeDiagnostic(code, message, severity, details, hint) {
42
+ return normalizeDiagnostic({ phase: phaseName, severity: severity || 'error', code, message, hint, details, loc: { file: '<node-schema-decode-result-proof>' } });
43
+ }
44
+ function sanitize(value) {
45
+ if (Array.isArray(value)) return value.map(sanitize);
46
+ if (!value || typeof value !== 'object') return value;
47
+ const out = {};
48
+ for (const [key, entry] of Object.entries(value)) {
49
+ if (key === 'body') { out.body = String(entry || ''); out.bodyPreview = String(entry || '').slice(0, 240); }
50
+ else out[key] = sanitize(entry);
51
+ }
52
+ return out;
53
+ }
54
+ function check(name, ok, details, failures, diagnostics) {
55
+ const entry = { name, status: ok ? 'ok' : 'error', details: sanitize(details || {}) };
56
+ if (!ok) {
57
+ failures.push(entry);
58
+ diagnostics.push(makeDiagnostic(schemaDecode.SCHEMA_DECODE_RESULT_DIAGNOSTICS.compiledSmokeFailed, `Node schema decode result proof check failed: ${name}.`, 'error', entry.details));
59
+ }
60
+ return entry;
61
+ }
62
+ function runRoute(runtime, requestJsonRouteIndex, variant) {
63
+ const suffix = variant === 'schema-missing' ? '_schema_missing' : variant === 'invalid' ? '_invalid' : '';
64
+ const fnName = `bridge_run_request_json_route_${requestJsonRouteIndex}${suffix}`;
65
+ const fn = runtime.exports[fnName];
66
+ if (typeof fn !== 'function') throw new Error(`Missing schema decode bridge export ${fnName}.`);
67
+ const status = fn();
68
+ return { status, result: runtime.readResult(), bodyReadCount: runtime.requestBodyReadCount(), parseCount: runtime.requestJsonParseCount() };
69
+ }
70
+
71
+ function buildNodeSchemaDecodeResultProof(options = {}) {
72
+ const cwd = options.cwd || process.cwd();
73
+ const outDir = options.outDir || getDefaultArtifactsDir(cwd);
74
+ const generatedBy = options.generatedBy || PACKAGE_VERSION;
75
+ const diagnostics = [];
76
+ const failures = [];
77
+ const checks = [];
78
+ const plan = normalizePlan(options.schemaDecodeResultPlan || options.plan);
79
+ if (!plan) diagnostics.push(makeDiagnostic(schemaDecode.SCHEMA_DECODE_RESULT_DIAGNOSTICS.planMissing, 'Node schema decode result proof requires schema-decode-result-plan.json.', 'error', { hint: 'Enable schema-decode-result-plan before requesting the Node proof.' }));
80
+ const routes = schemaRoutes(plan || {});
81
+ if (plan && routes.length === 0) diagnostics.push(makeDiagnostic(schemaDecode.SCHEMA_DECODE_RESULT_DIAGNOSTICS.noSchemaRoutes, 'Node schema decode result proof requires at least one route using ctx.req.json("Schema") or ctx.req.parse("Schema").', 'error', { routes: (plan.routes || []).length }));
82
+ const wasmFile = findCompiledHandlerWasm(options.compiledHandlers, cwd, outDir, options.compiledHandlersCwd || options.sourceCwd);
83
+ if (!wasmFile) diagnostics.push(makeDiagnostic(schemaDecode.SCHEMA_DECODE_RESULT_DIAGNOSTICS.wasmMissing, 'Node schema decode result proof requires the compiled-handler Wasm artifact.', 'error', { hint: 'Enable compiled handlers together with the schema decode result proof.' }));
84
+
85
+ const smoke = { results: {}, checks, failedChecks: failures };
86
+ if (wasmFile && diagnostics.filter((entry) => (entry.severity || 'error') === 'error').length === 0) {
87
+ try {
88
+ const runtime = createCompiledHandlerBridgeRuntime(wasmFile);
89
+ for (let index = 0; index < routes.length; index += 1) {
90
+ const route = routes[index];
91
+ const requestJsonRouteIndex = Number(route.requestJsonRouteIndex) | 0;
92
+ const valid = runRoute(runtime, requestJsonRouteIndex, 'valid');
93
+ smoke.results[`route${index}`] = sanitize({ route: { method: route.method, path: route.path, handlerName: route.handlerName, requestJsonRouteIndex }, valid });
94
+ const validReturnedResolveBoundary = valid.result && valid.result.kind === 'resolve';
95
+ checks.push(check(`schema_decode_route_${index}_valid_status`, (valid.status >= 200 && valid.status < 300) || (validReturnedResolveBoundary && valid.status === 0), valid, failures, diagnostics));
96
+ checks.push(check(`schema_decode_route_${index}_lazy_read_once`, valid.bodyReadCount === 1, valid, failures, diagnostics));
97
+ checks.push(check(`schema_decode_route_${index}_parse_once`, valid.parseCount === 1, valid, failures, diagnostics));
98
+ if (validReturnedResolveBoundary) {
99
+ checks.push(check(`schema_decode_route_${index}_effect_boundary_returned`, true, valid.result, failures, diagnostics));
100
+ } else {
101
+ checks.push(check(`schema_decode_route_${index}_body_fields_visible`, /body-123/.test(valid.result.body || '') && /Body User/.test(valid.result.body || '') && /true/.test(valid.result.body || ''), valid.result, failures, diagnostics));
102
+ }
103
+ const invalid = runRoute(runtime, requestJsonRouteIndex, 'invalid');
104
+ smoke.results[`route${index}InvalidJson`] = sanitize({ route: { method: route.method, path: route.path, handlerName: route.handlerName, requestJsonRouteIndex }, invalid });
105
+ checks.push(check(`schema_decode_route_${index}_invalid_json_status`, invalid.status === 400, invalid, failures, diagnostics));
106
+ checks.push(check(`schema_decode_route_${index}_invalid_json_error`, /invalid_json/.test(invalid.result.body || ''), invalid.result, failures, diagnostics));
107
+ const missing = runRoute(runtime, requestJsonRouteIndex, 'schema-missing');
108
+ smoke.results[`route${index}MissingField`] = sanitize({ route: { method: route.method, path: route.path, handlerName: route.handlerName, requestJsonRouteIndex }, missing });
109
+ checks.push(check(`schema_decode_route_${index}_missing_field_status`, missing.status === 400, missing, failures, diagnostics));
110
+ checks.push(check(`schema_decode_route_${index}_missing_field_error`, /schema_decode_failed/.test(missing.result.body || '') && /active/.test(missing.result.body || ''), missing.result, failures, diagnostics));
111
+ }
112
+ checks.push(check('schema_decode_module_has_no_js_handler_imports', runtime.imports.filter((entry) => entry.module === 'pulsewasm_handlers').length === 0, { imports: runtime.imports }, failures, diagnostics));
113
+ } catch (error) {
114
+ diagnostics.push(makeDiagnostic(schemaDecode.SCHEMA_DECODE_RESULT_DIAGNOSTICS.compiledSmokeFailed, 'Node schema decode result proof smoke execution failed.', 'error', { message: error && error.message ? error.message : String(error), stack: error && error.stack ? error.stack : undefined }));
115
+ }
116
+ }
117
+ const errorDiagnostics = diagnostics.filter((entry) => (entry.severity || 'error') === 'error');
118
+ const compiledHandlerFiles = Array.isArray(options.compiledHandlers && options.compiledHandlers.files) ? options.compiledHandlers.files : [];
119
+ const compiledHandlerOutputs = Array.isArray(options.compiledHandlers && options.compiledHandlers.outputFiles) ? options.compiledHandlers.outputFiles : [];
120
+ const artifact = normalizeArtifact({
121
+ version: schemaDecode.NODE_SCHEMA_DECODE_RESULT_PROOF_VERSION,
122
+ generatedBy,
123
+ phase: schemaDecode.NODE_SCHEMA_DECODE_RESULT_PROOF_PHASE,
124
+ artifact: schemaDecode.NODE_SCHEMA_DECODE_RESULT_PROOF_ARTIFACT,
125
+ contractId: schemaDecode.SCHEMA_DECODE_RESULT_CONTRACT_ID,
126
+ provider: 'node',
127
+ status: errorDiagnostics.length === 0 ? 'ok' : 'error',
128
+ sourcePlanVersion: plan && plan.version,
129
+ sourcePlanArtifact: plan && plan.artifact,
130
+ schemaDecodeResultPlanConnected: Boolean(plan),
131
+ compiledHandlersConnected: Boolean(options.compiledHandlers),
132
+ compiledHandlerPlanVersion: options.compiledHandlers && options.compiledHandlers.artifact && options.compiledHandlers.artifact.version,
133
+ runtime: { version: schemaDecode.NODE_SCHEMA_DECODE_RESULT_RUNTIME_VERSION, package: '@pulse-compute/wasm-runtime-core-as/compiler/compiled-handlers', wasm: wasmFile ? path.relative(cwd, wasmFile).replace(/\\/g, '/') : undefined },
134
+ scope: schemaDecode.defaultNodeSchemaDecodeResultProofPolicy(),
135
+ surface: JSON.parse(JSON.stringify(schemaDecode.SCHEMA_DECODE_RESULT_SURFACE)),
136
+ accessors: JSON.parse(JSON.stringify(schemaDecode.SCHEMA_DECODE_RESULT_ACCESSORS)),
137
+ reserved: [...schemaDecode.SCHEMA_DECODE_RESULT_RESERVED_SURFACE],
138
+ compiledWasmRuntimeImplemented: errorDiagnostics.length === 0,
139
+ nodeCompiledProofImplemented: errorDiagnostics.length === 0,
140
+ providerNetworkFetchRequired: false,
141
+ automaticSchemaGeneration: false,
142
+ typedSchemaSpecificAccessors: false,
143
+ arbitraryJsObjectInference: false,
144
+ binaryRequestBodyParsing: false,
145
+ streamRequestBodyParsing: false,
146
+ asyncAwait: false,
147
+ promises: false,
148
+ asyncify: false,
149
+ routesConsumed: routes.map((route, index) => ({ index, requestJsonRouteIndex: route.requestJsonRouteIndex, routeId: route.routeId, method: route.method, path: route.path, handlerName: route.handlerName, decodes: route.decodes })),
150
+ smoke,
151
+ summary: {
152
+ schemaDecodeRoutes: routes.length,
153
+ compiled: Boolean(wasmFile) && errorDiagnostics.length === 0,
154
+ executed: checks.length > 0 && errorDiagnostics.length === 0,
155
+ checks: checks.length,
156
+ failedChecks: failures.length,
157
+ lazyReadValidated: checks.some((entry) => entry.name.includes('lazy_read_once') && entry.status === 'ok'),
158
+ parseCacheValidated: checks.some((entry) => entry.name.includes('parse_once') && entry.status === 'ok'),
159
+ invalidJsonValidated: checks.some((entry) => entry.name.includes('invalid_json_status') && entry.status === 'ok'),
160
+ missingFieldValidated: checks.some((entry) => entry.name.includes('missing_field_error') && entry.status === 'ok'),
161
+ noJsHandlerImports: checks.some((entry) => entry.name === 'schema_decode_module_has_no_js_handler_imports' && entry.status === 'ok'),
162
+ compiledWasmRuntimeImplemented: errorDiagnostics.length === 0,
163
+ nodeCompiledProofImplemented: errorDiagnostics.length === 0,
164
+ automaticSchemaGeneration: false,
165
+ typedSchemaSpecificAccessors: false,
166
+ arbitraryJsObjectInference: false,
167
+ binaryRequestBodyParsing: false,
168
+ streamRequestBodyParsing: false,
169
+ diagnostics: diagnostics.length,
170
+ errorDiagnostics: errorDiagnostics.length,
171
+ errors: errorDiagnostics.length
172
+ },
173
+ generatedFiles: compiledHandlerFiles.map((file) => ({ file: file.file, bytes: file.bytes, sha256: file.sha256 })),
174
+ generatedOutputs: compiledHandlerOutputs.map((file) => ({ file: file.file, kind: file.kind, bytes: file.bytes, sha256: file.sha256 })),
175
+ diagnostics
176
+ }, cwd);
177
+ return { artifact, diagnostics, failures, files: compiledHandlerFiles, outputFiles: compiledHandlerOutputs.length ? compiledHandlerOutputs : (wasmFile ? [{ file: path.relative(cwd, wasmFile).replace(/\\/g, '/'), kind: 'compiled-handler-wasm', bytes: fs.statSync(wasmFile).size }] : []), wasmFile };
178
+ }
179
+
180
+ module.exports = { phaseName, buildNodeSchemaDecodeResultProof };