@pulse-compute/wasm-compiler 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 (104) hide show
  1. package/README.md +53 -1
  2. package/bin/provider-proof-composition.js +34 -0
  3. package/bin/pulsewasm-extract.js +20 -0
  4. package/package.json +60 -5
  5. package/src/artifacts-dir.js +13 -0
  6. package/src/ast-json.js +52 -0
  7. package/src/build-manifest.js +354 -0
  8. package/src/canonical-api-compiler.js +269 -0
  9. package/src/canonical-native-compiler.js +411 -0
  10. package/src/canonical-native-plan.js +1478 -0
  11. package/src/canonical-project-compiler.js +1224 -0
  12. package/src/canonical-router-compiler.js +25 -0
  13. package/src/cli-intents.js +1235 -0
  14. package/src/cli.js +1927 -0
  15. package/src/codegen/assemblyscript-compile.js +3 -0
  16. package/src/codegen/assemblyscript-core.js +3 -0
  17. package/src/codegen/assemblyscript-shape.js +3 -0
  18. package/src/codegen/assemblyscript-wasm-smoke.js +3 -0
  19. package/src/codegen/backend-capabilities.js +3 -0
  20. package/src/codegen/channel-broadcaster.js +3 -0
  21. package/src/codegen/compiled-handlers.js +3 -0
  22. package/src/codegen/compiled-wasm-runtime.js +3 -0
  23. package/src/codegen/config-references.js +248 -0
  24. package/src/codegen/dispatch-ts.js +145 -0
  25. package/src/codegen/effect-composition.js +331 -0
  26. package/src/codegen/effect-runtime.js +418 -0
  27. package/src/codegen/execution-harness-ts.js +467 -0
  28. package/src/codegen/handler-bindings-ts.js +298 -0
  29. package/src/codegen/handler-library-contracts.js +3 -0
  30. package/src/codegen/host-capabilities.js +3 -0
  31. package/src/codegen/host-runtime-kernel.js +3 -0
  32. package/src/codegen/integrated-compiled-app.js +3 -0
  33. package/src/codegen/json-body.js +3 -0
  34. package/src/codegen/library-sidecars.js +3 -0
  35. package/src/codegen/local-harness-ts.js +453 -0
  36. package/src/codegen/pulse-wrapper.js +217 -0
  37. package/src/codegen/request-result-headers.js +3 -0
  38. package/src/codegen/schema-json-compile.js +3 -0
  39. package/src/codegen/schema-json-sidecar-v2.js +3 -0
  40. package/src/codegen/schema-json-sidecar.js +3 -0
  41. package/src/codegen/streaming-passthrough.js +3 -0
  42. package/src/codegen/wasm-host-abi.js +3 -0
  43. package/src/codegen/wasm-host-bridge.js +3 -0
  44. package/src/compiled-wasm-host-runtime-kv.js +3 -0
  45. package/src/config-resolver.js +813 -0
  46. package/src/crypto-requirement-planner.js +89 -0
  47. package/src/definitions/config-schema.js +14 -0
  48. package/src/definitions/handler-roles.js +14 -0
  49. package/src/definitions/path-grammar.js +14 -0
  50. package/src/definitions/router-api.js +14 -0
  51. package/src/diagnostics/codes.js +14 -0
  52. package/src/diagnostics/reporter.js +14 -0
  53. package/src/diagnostics.js +14 -0
  54. package/src/dispatch-table.js +400 -0
  55. package/src/events/event-emit.js +265 -0
  56. package/src/events/event-topology.js +127 -0
  57. package/src/execution-plan.js +463 -0
  58. package/src/extractor.js +2816 -0
  59. package/src/handler-eval.js +1154 -0
  60. package/src/handler-table.js +326 -0
  61. package/src/index.js +19 -0
  62. package/src/javascript-application-plan.js +181 -0
  63. package/src/kv-provider.js +3 -0
  64. package/src/path-table.js +60 -0
  65. package/src/path.js +14 -0
  66. package/src/patterns/config-define.js +30 -0
  67. package/src/patterns/dependency-call.js +18 -0
  68. package/src/patterns/env-lookup.js +13 -0
  69. package/src/patterns/handler-reference.js +29 -0
  70. package/src/patterns/path-literal.js +35 -0
  71. package/src/patterns/result.js +15 -0
  72. package/src/patterns/router-chain-call.js +50 -0
  73. package/src/patterns/router-construction.js +19 -0
  74. package/src/project/package-reachability.js +782 -0
  75. package/src/project/reachable-graph-builder.js +992 -0
  76. package/src/project/reachable-graph-contract.js +54 -0
  77. package/src/project/reachable-graph-implementation.js +36 -0
  78. package/src/project/router-module-linker.js +710 -0
  79. package/src/project-config-compiler.js +222 -0
  80. package/src/project-target-support.js +500 -0
  81. package/src/provider-toolchain.js +299 -0
  82. package/src/spine/async-surface-normalizer.js +328 -0
  83. package/src/spine/canonical-handler-ir.js +336 -0
  84. package/src/spine/canonical-native-module.js +87 -0
  85. package/src/spine/canonical-native-plan.js +89 -0
  86. package/src/spine/canonical-project.js +76 -0
  87. package/src/spine/canonical-router.js +155 -0
  88. package/src/spine/canonical-source.js +165 -0
  89. package/src/spine/diagnostic-authority.js +290 -0
  90. package/src/spine/equivalence.js +262 -0
  91. package/src/spine/guest-unit-stage.js +87 -0
  92. package/src/spine/handler-ir-emitter.js +455 -0
  93. package/src/spine/handler-ir-managed.js +1598 -0
  94. package/src/spine/handler-ir.js +797 -0
  95. package/src/spine/handler-surface-authority.js +588 -0
  96. package/src/spine/package-operation-seam.js +1015 -0
  97. package/src/spine/pipeline.js +202 -0
  98. package/src/spine/plain-handler-frontend.js +545 -0
  99. package/src/spine/provider-requirement-authority.js +208 -0
  100. package/src/spine/router-control-contract.js +17 -0
  101. package/src/spine/router-handler-frontend.js +514 -0
  102. package/src/spine/router-handler-ir.js +372 -0
  103. package/src/spine/router-topology-frontend.js +638 -0
  104. package/src/stable-id.js +14 -0
@@ -0,0 +1,467 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const { PACKAGE_VERSION, normalizeArtifact } = require('../diagnostics.js');
5
+ const { EXECUTION_PLAN_VERSION } = loadContractsExecutionPlan();
6
+
7
+ const EXECUTION_HARNESS_VERSION = 'pulsewasm.execution-harness.v2';
8
+
9
+ function loadContractsExecutionPlan() {
10
+ try {
11
+ return require('@pulse-compute/wasm-contracts/execution-plan');
12
+ } catch (error) {
13
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
14
+ return require('../../../contracts/src/execution-plan.js');
15
+ }
16
+ throw error;
17
+ }
18
+ }
19
+
20
+ function sha256(text) {
21
+ return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
22
+ }
23
+
24
+ function tsJson(value) {
25
+ return JSON.stringify(value, null, 2)
26
+ .replace(/</g, '\\u003c')
27
+ .replace(/>/g, '\\u003e')
28
+ .replace(/&/g, '\\u0026');
29
+ }
30
+
31
+ function header(file = 'execution harness') {
32
+ return [
33
+ '/*',
34
+ ' * Generated by PulseWasm Phase 8E.',
35
+ ' * Do not edit by hand.',
36
+ ' *',
37
+ ` * This ${file} is generated from execution-plan.json + handler-bindings.json.`,
38
+ ' * It executes ordered entry metadata and must not use the original Router.',
39
+ ' */',
40
+ ''
41
+ ].join('\n');
42
+ }
43
+
44
+ function makeFile(file, text) {
45
+ return {
46
+ file,
47
+ bytes: Buffer.byteLength(text, 'utf8'),
48
+ sha256: sha256(text),
49
+ text
50
+ };
51
+ }
52
+
53
+ function uniqueNumbers(values) {
54
+ return Array.from(new Set(values.filter((value) => Number.isInteger(value)))).sort((a, b) => a - b);
55
+ }
56
+
57
+ function collectExpectedSlots(dispatchTable, executionPlan) {
58
+ const slots = [];
59
+ if (executionPlan && Array.isArray(executionPlan.entries)) {
60
+ for (const entry of executionPlan.entries) {
61
+ if (Number.isInteger(entry.handler?.slot)) slots.push(entry.handler.slot);
62
+ if (Number.isInteger(entry.channel?.slot)) slots.push(entry.channel.slot);
63
+ }
64
+ for (const scope of executionPlan.scopes || []) {
65
+ for (const step of scope.connect || []) if (Number.isInteger(step.slot)) slots.push(step.slot);
66
+ for (const step of scope.disconnect || []) if (Number.isInteger(step.slot)) slots.push(step.slot);
67
+ if (Number.isInteger(scope.channel?.slot)) slots.push(scope.channel.slot);
68
+ }
69
+ return uniqueNumbers(slots);
70
+ }
71
+
72
+ for (const route of dispatchTable?.routes || []) {
73
+ for (const step of route.execution?.normal || []) if (Number.isInteger(step.slot)) slots.push(step.slot);
74
+ for (const step of route.execution?.error || []) if (Number.isInteger(step.slot)) slots.push(step.slot);
75
+ for (const step of route.execution?.lifecycle?.connect || []) if (Number.isInteger(step.slot)) slots.push(step.slot);
76
+ for (const step of route.execution?.lifecycle?.disconnect || []) if (Number.isInteger(step.slot)) slots.push(step.slot);
77
+ const channel = route.execution?.channel;
78
+ if (channel && channel.kind === 'handler' && Number.isInteger(channel.slot)) slots.push(channel.slot);
79
+ }
80
+ return uniqueNumbers(slots);
81
+ }
82
+
83
+ function buildExecutionPlanSource(executionPlan) {
84
+ return `${header('execution plan module')}export const EXECUTION_PLAN_VERSION = ${tsJson(executionPlan?.version || EXECUTION_PLAN_VERSION)} as const\n\nexport const EXECUTION_PLAN = ${tsJson(executionPlan || { entries: [], scopes: [] })} as const\n\nexport const EXECUTION_ENTRIES = EXECUTION_PLAN.entries\n\nexport const EXECUTION_SCOPES = EXECUTION_PLAN.scopes\n`;
85
+ }
86
+
87
+ function buildExecutionHarnessSource(expectedSlots) {
88
+ return `${header()}import { match, matchPathPattern } from './dispatch'
89
+ import type { DispatchMatch, HandlerUse } from './types'
90
+ import { EXECUTION_ENTRIES, EXECUTION_SCOPES } from './execution-plan'
91
+ import { getHandlerBySlot, assertHandlerBindingCoverage, type PulseHandler } from './handler-slots'
92
+
93
+ export type ExecutionMode = 'entry-scan'
94
+ export type ExecutionPhase = 'normal' | 'error' | 'lifecycle:connect' | 'lifecycle:disconnect' | 'channel'
95
+ export type ExecutionStatus = 'response' | 'halt' | 'continue' | 'error' | 'not-found'
96
+
97
+ export type ExecutionTraceEvent = {
98
+ readonly kind: 'enter' | 'next' | 'exit' | 'halt' | 'skip' | 'channel' | 'scope' | 'mount'
99
+ readonly phase: ExecutionPhase
100
+ readonly index: number
101
+ readonly slot?: number
102
+ readonly handlerId?: string
103
+ readonly name?: string
104
+ readonly error?: boolean
105
+ }
106
+
107
+ export type ExecutionContext = {
108
+ readonly method: string
109
+ readonly path: string
110
+ params: Record<string, string>
111
+ locals: Record<string, unknown>
112
+ trace: ExecutionTraceEvent[]
113
+ scope?: unknown
114
+ activeError?: unknown
115
+ [key: string]: unknown
116
+ }
117
+
118
+ export type HandlerResolver = (slot: number) => PulseHandler | undefined
119
+
120
+ export type ExecuteOptions = {
121
+ readonly ctx?: ExecutionContext
122
+ readonly getHandlerBySlot?: HandlerResolver
123
+ readonly trace?: ExecutionTraceEvent[]
124
+ }
125
+
126
+ export type ExecutionResult = {
127
+ readonly status: ExecutionStatus
128
+ readonly value?: unknown
129
+ readonly error?: unknown
130
+ readonly ctx: ExecutionContext
131
+ readonly trace: readonly ExecutionTraceEvent[]
132
+ readonly completed: boolean
133
+ readonly executedSlots: readonly number[]
134
+ readonly finalIndex: number
135
+ }
136
+
137
+ export const EXPECTED_HANDLER_SLOTS = ${tsJson(expectedSlots)} as const
138
+
139
+ type AnyEntry = typeof EXECUTION_ENTRIES[number]
140
+ type AnyScope = typeof EXECUTION_SCOPES[number]
141
+
142
+ function slotFor(step: HandlerUse | { readonly slot?: number | null; readonly name?: string; readonly id?: string; readonly handlerId?: string }, phase: ExecutionPhase): number {
143
+ if (!Number.isInteger(step.slot)) {
144
+ throw new Error('Missing handler slot for ' + phase + ' step ' + String(step.name || step.handlerId || step.id || '<unknown>'))
145
+ }
146
+ return step.slot as number
147
+ }
148
+
149
+ function handlerIdFor(step: HandlerUse | { readonly id?: string; readonly handlerId?: string }): string | undefined {
150
+ return step.handlerId || step.id
151
+ }
152
+
153
+ function ensureSync(result: unknown, name: string | undefined): void {
154
+ if (result && typeof (result as { then?: unknown }).then === 'function') {
155
+ throw new Error('PulseWasm v1 execution harness received a Promise from handler ' + String(name || '<unknown>'))
156
+ }
157
+ }
158
+
159
+ function traceEvent(kind: ExecutionTraceEvent['kind'], phase: ExecutionPhase, index: number, step?: { readonly slot?: number | null; readonly id?: string; readonly handlerId?: string; readonly name?: string }, error?: boolean): ExecutionTraceEvent {
160
+ return {
161
+ kind,
162
+ phase,
163
+ index,
164
+ slot: step && Number.isInteger(step.slot) ? step.slot as number : undefined,
165
+ handlerId: step ? handlerIdFor(step) : undefined,
166
+ name: step?.name,
167
+ error
168
+ }
169
+ }
170
+
171
+ function scopeById(scopeId: number | null | undefined): AnyScope | undefined {
172
+ if (!Number.isInteger(scopeId)) return undefined
173
+ return (EXECUTION_SCOPES as readonly AnyScope[]).find((scope) => scope.id === scopeId)
174
+ }
175
+
176
+ function mergeParams(target: Record<string, string>, next: Record<string, string>): void {
177
+ for (const [key, value] of Object.entries(next || {})) target[key] = value
178
+ }
179
+
180
+ function matchEntryPattern(entry: AnyEntry, requestPath: string): { params: Record<string, string>; rest: string } | null {
181
+ const pattern = (entry as { pattern?: unknown }).pattern
182
+ if (!pattern) return { params: {}, rest: requestPath }
183
+ return matchPathPattern(pattern as never, requestPath)
184
+ }
185
+
186
+ function createTraceBackedContext(method: string, requestPath: string, extra: Record<string, unknown> = {}): ExecutionContext {
187
+ return {
188
+ ...extra,
189
+ method: String(method || '').toUpperCase(),
190
+ path: requestPath,
191
+ params: { ...(extra.params as Record<string, string> || {}) },
192
+ locals: Object.assign({}, extra.locals || {}),
193
+ trace: []
194
+ }
195
+ }
196
+
197
+ export function createExecutionContext(method: string, requestPath: string, extra: Record<string, unknown> = {}): ExecutionContext {
198
+ return createTraceBackedContext(method, requestPath, extra)
199
+ }
200
+
201
+ export function assertExecutionBindingCoverage(expectedSlots: readonly number[] = EXPECTED_HANDLER_SLOTS): void {
202
+ assertHandlerBindingCoverage(expectedSlots)
203
+ }
204
+
205
+ export function execute(method: string, requestPath: string, options: ExecuteOptions = {}): ExecutionResult {
206
+ const normalizedMethod = String(method || '').toUpperCase()
207
+ const ctx = options.ctx || createExecutionContext(normalizedMethod, requestPath)
208
+ ctx.method = normalizedMethod
209
+ ctx.path = requestPath
210
+ const trace = options.trace || ctx.trace || []
211
+ ctx.trace = trace
212
+ const resolver = options.getHandlerBySlot || getHandlerBySlot
213
+ const executedSlots: number[] = []
214
+
215
+ function done(status: ExecutionStatus, finalIndex: number, value?: unknown, error?: unknown): ExecutionResult {
216
+ return { status, value, error, ctx, trace, completed: status === 'response' || status === 'halt' || status === 'error' || status === 'not-found', executedSlots, finalIndex }
217
+ }
218
+
219
+ function scan(startIndex: number, activeError?: unknown): ExecutionResult {
220
+ let index = startIndex
221
+ while (index < EXECUTION_ENTRIES.length) {
222
+ const entry = EXECUTION_ENTRIES[index] as AnyEntry
223
+ const kind = (entry as { kind: string }).kind
224
+ const nextIndex = Number((entry as { nextIndex?: number }).nextIndex ?? index + 1)
225
+
226
+ if (activeError !== undefined) {
227
+ if (kind !== 'error') {
228
+ trace.push(traceEvent('skip', 'error', index, undefined, true))
229
+ index = kind === 'mount' ? Number((entry as { parentContinueIndex?: number }).parentContinueIndex ?? nextIndex) : nextIndex
230
+ continue
231
+ }
232
+
233
+ const handlerUse = (entry as { handler?: HandlerUse }).handler
234
+ if (!handlerUse) {
235
+ index = nextIndex
236
+ continue
237
+ }
238
+ const slot = slotFor(handlerUse, 'error')
239
+ const handler = resolver(slot)
240
+ if (!handler) throw new Error('Missing generated error handler binding for slot ' + slot)
241
+ executedSlots.push(slot)
242
+ ctx.scope = scopeById((entry as { scopeId?: number }).scopeId)
243
+ ctx.activeError = activeError
244
+ trace.push(traceEvent('enter', 'error', index, handlerUse, true))
245
+ let didNext = false
246
+ let nextResult: ExecutionResult | undefined
247
+ const next = (nextError?: unknown): unknown => {
248
+ didNext = true
249
+ trace.push(traceEvent('next', nextError === undefined ? 'normal' : 'error', index, handlerUse, nextError !== undefined))
250
+ nextResult = scan(nextIndex, nextError)
251
+ return nextResult.value
252
+ }
253
+ try {
254
+ const out = handler(activeError, ctx, next)
255
+ ensureSync(out, handlerUse.name || handlerIdFor(handlerUse))
256
+ trace.push(traceEvent('exit', 'error', index, handlerUse, activeError !== undefined))
257
+ if (out !== undefined) return done('response', index, out)
258
+ if (didNext) return nextResult || done('continue', nextIndex)
259
+ index = nextIndex
260
+ continue
261
+ } catch (err) {
262
+ activeError = err
263
+ index = nextIndex
264
+ continue
265
+ }
266
+ }
267
+
268
+ if (kind === 'error') {
269
+ index = nextIndex
270
+ continue
271
+ }
272
+
273
+ if (kind === 'channel' || kind === 'lifecycle') {
274
+ trace.push(traceEvent('scope', 'normal', index))
275
+ index = nextIndex
276
+ continue
277
+ }
278
+
279
+ if (kind === 'mount') {
280
+ const matched = matchEntryPattern(entry, requestPath)
281
+ if (!matched) {
282
+ trace.push(traceEvent('mount', 'normal', index))
283
+ index = Number((entry as { parentContinueIndex?: number }).parentContinueIndex ?? nextIndex)
284
+ } else {
285
+ mergeParams(ctx.params, matched.params)
286
+ index = Number((entry as { childStartIndex?: number }).childStartIndex ?? nextIndex)
287
+ }
288
+ continue
289
+ }
290
+
291
+ if (kind !== 'use' && kind !== 'route') {
292
+ index = nextIndex
293
+ continue
294
+ }
295
+
296
+ if (kind === 'route' && String((entry as { method?: string }).method || '').toUpperCase() !== normalizedMethod) {
297
+ index = nextIndex
298
+ continue
299
+ }
300
+
301
+ const matched = matchEntryPattern(entry, requestPath)
302
+ if (!matched) {
303
+ index = nextIndex
304
+ continue
305
+ }
306
+ mergeParams(ctx.params, matched.params)
307
+ const handlerUse = (entry as { handler?: HandlerUse }).handler
308
+ if (!handlerUse) {
309
+ index = nextIndex
310
+ continue
311
+ }
312
+ const slot = slotFor(handlerUse, kind === 'route' ? 'normal' : 'normal')
313
+ const handler = resolver(slot)
314
+ if (!handler) throw new Error('Missing generated handler binding for slot ' + slot)
315
+ executedSlots.push(slot)
316
+ ctx.scope = scopeById((entry as { scopeId?: number }).scopeId)
317
+ trace.push(traceEvent('enter', 'normal', index, handlerUse))
318
+ let didNext = false
319
+ let nextResult: ExecutionResult | undefined
320
+ const next = (err?: unknown): unknown => {
321
+ didNext = true
322
+ trace.push(traceEvent('next', err === undefined ? 'normal' : 'error', index, handlerUse, err !== undefined))
323
+ nextResult = scan(nextIndex, err)
324
+ return nextResult.value
325
+ }
326
+ try {
327
+ const out = handler(ctx, next)
328
+ ensureSync(out, handlerUse.name || handlerIdFor(handlerUse))
329
+ trace.push(traceEvent('exit', 'normal', index, handlerUse))
330
+ if (out !== undefined) return done('response', index, out)
331
+ if (didNext) return nextResult || done('continue', nextIndex)
332
+ trace.push(traceEvent('halt', 'normal', index, handlerUse))
333
+ return done('halt', index)
334
+ } catch (err) {
335
+ return scan(nextIndex, err)
336
+ }
337
+ }
338
+ if (activeError !== undefined) return done('error', index, undefined, activeError)
339
+ return done('not-found', index)
340
+ }
341
+
342
+ return scan(0, undefined)
343
+ }
344
+
345
+ export function executeMatch(matchResult: DispatchMatch, options: ExecuteOptions = {}): ExecutionResult {
346
+ const requestPath = (matchResult as unknown as { requestPath?: string }).requestPath || matchResult.path
347
+ return execute(matchResult.method, requestPath, options)
348
+ }
349
+
350
+ export function executeLifecycle(matchResult: DispatchMatch, event: 'connect' | 'disconnect', options: ExecuteOptions = {}): ExecutionResult {
351
+ const ctx = options.ctx || createExecutionContext(matchResult.method, (matchResult as unknown as { requestPath?: string }).requestPath || matchResult.path)
352
+ const steps = event === 'connect' ? matchResult.execution.lifecycle.connect : matchResult.execution.lifecycle.disconnect
353
+ const trace = options.trace || ctx.trace || []
354
+ ctx.trace = trace
355
+ const resolver = options.getHandlerBySlot || getHandlerBySlot
356
+ const executedSlots: number[] = []
357
+ for (let i = 0; i < steps.length; i += 1) {
358
+ const step = steps[i]
359
+ const slot = slotFor(step, event === 'connect' ? 'lifecycle:connect' : 'lifecycle:disconnect')
360
+ const handler = resolver(slot)
361
+ if (!handler) throw new Error('Missing generated lifecycle handler binding for slot ' + slot)
362
+ executedSlots.push(slot)
363
+ trace.push(traceEvent('enter', event === 'connect' ? 'lifecycle:connect' : 'lifecycle:disconnect', i, step))
364
+ const out = handler(ctx, () => undefined)
365
+ ensureSync(out, step.name || handlerIdFor(step))
366
+ trace.push(traceEvent('exit', event === 'connect' ? 'lifecycle:connect' : 'lifecycle:disconnect', i, step))
367
+ }
368
+ return { status: 'continue', ctx, trace, completed: true, executedSlots, finalIndex: steps.length }
369
+ }
370
+
371
+ export function resolveChannel(matchResult: DispatchMatch, options: ExecuteOptions = {}): unknown {
372
+ const channel = matchResult.execution.channel
373
+ if (!channel) return undefined
374
+ if (channel.kind === 'static') return channel.value
375
+
376
+ const resolver = options.getHandlerBySlot || getHandlerBySlot
377
+ const slot = slotFor(channel as HandlerUse, 'channel')
378
+ const handler = resolver(slot)
379
+ if (!handler) throw new Error('Missing generated channel handler binding for slot ' + slot)
380
+ const ctx = options.ctx || createExecutionContext(matchResult.method, (matchResult as unknown as { requestPath?: string }).requestPath || matchResult.path)
381
+ const trace = options.trace || ctx.trace || []
382
+ trace.push(traceEvent('channel', 'channel', 0, channel as HandlerUse))
383
+ const result = handler(ctx)
384
+ ensureSync(result, (channel as HandlerUse).name || handlerIdFor(channel as HandlerUse))
385
+ return result
386
+ }
387
+
388
+ export { match }
389
+ `;
390
+ }
391
+
392
+ function buildTypeScriptExecutionHarness(dispatchTable, handlerBindings, options = {}) {
393
+ const cwd = options.cwd || process.cwd();
394
+ const diagnostics = [];
395
+ const executionPlan = options.executionPlan;
396
+
397
+ if (!handlerBindings) {
398
+ diagnostics.push({
399
+ pass: 'execution-harness',
400
+ code: 'PULSEWASM_EXECUTION_HARNESS_REQUIRES_HANDLER_BINDINGS',
401
+ severity: 'error',
402
+ message: 'The generated execution harness requires generated handler slot bindings.',
403
+ hint: 'Enable --emit-handler-bindings or use --emit-execution-harness, which implies handler bindings.',
404
+ loc: { file: '<execution-harness>' }
405
+ });
406
+ }
407
+
408
+ if (!executionPlan) {
409
+ diagnostics.push({
410
+ pass: 'execution-harness',
411
+ code: 'PULSEWASM_EXECUTION_HARNESS_REQUIRES_EXECUTION_PLAN',
412
+ severity: 'error',
413
+ message: 'The Phase 8E execution harness requires execution-plan.json.',
414
+ hint: 'Run the execution-plan pass before generated harness emission.',
415
+ loc: { file: '<execution-harness>' }
416
+ });
417
+ }
418
+
419
+ const expectedSlots = collectExpectedSlots(dispatchTable, executionPlan);
420
+ const files = [
421
+ makeFile('generated/execution-plan.ts', buildExecutionPlanSource(executionPlan)),
422
+ makeFile('generated/execution-harness.ts', buildExecutionHarnessSource(expectedSlots))
423
+ ];
424
+
425
+ const artifact = normalizeArtifact({
426
+ version: EXECUTION_HARNESS_VERSION,
427
+ generatedBy: options.generatedBy || PACKAGE_VERSION,
428
+ source: dispatchTable?.source,
429
+ dispatchTableVersion: dispatchTable?.version,
430
+ executionPlanVersion: executionPlan?.version,
431
+ handlerBindingsVersion: handlerBindings?.artifact?.version,
432
+ policy: {
433
+ phase: '8E',
434
+ sourceOfTruth: 'execution-plan.json + handler-bindings.json',
435
+ usesOriginalRouter: false,
436
+ requiresHandlerBindings: true,
437
+ defaultExecutionMode: 'entry-scan',
438
+ preservesRouteNext: true,
439
+ preservesNextErrorTransition: true,
440
+ generatedDispatchExecutes: true,
441
+ wasmCodegen: false
442
+ },
443
+ expectedHandlerSlots: expectedSlots,
444
+ output: {
445
+ language: 'typescript',
446
+ module: 'esm',
447
+ files: files.map(({ file, bytes, sha256: hash }) => ({ file, bytes, sha256: hash }))
448
+ },
449
+ summary: {
450
+ entries: executionPlan?.summary?.entries || 0,
451
+ routes: executionPlan?.summary?.routes || 0,
452
+ errors: executionPlan?.summary?.errors || 0,
453
+ scopes: executionPlan?.summary?.scopes || 0,
454
+ expectedHandlerSlots: expectedSlots.length,
455
+ handlerBindings: handlerBindings?.artifact?.summary?.bindings || 0,
456
+ diagnostics: diagnostics.length
457
+ }
458
+ }, cwd);
459
+
460
+ return { version: EXECUTION_HARNESS_VERSION, artifact, files, diagnostics };
461
+ }
462
+
463
+ module.exports = {
464
+ EXECUTION_HARNESS_VERSION,
465
+ buildTypeScriptExecutionHarness,
466
+ collectExpectedSlots
467
+ };