@pulse-compute/wasm-contracts 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 (97) hide show
  1. package/README.md +35 -2
  2. package/package.json +124 -6
  3. package/src/assets/contracts.js +296 -0
  4. package/src/config/provider-resolution.js +54 -0
  5. package/src/config/runtime-provider-kv.js +73 -0
  6. package/src/config/symbolic-refs.js +192 -0
  7. package/src/crypto/contracts.js +647 -0
  8. package/src/definitions/config-schema.js +30 -0
  9. package/src/definitions/handler-roles.js +66 -0
  10. package/src/definitions/path-grammar.js +11 -0
  11. package/src/definitions/router-api.js +149 -0
  12. package/src/diagnostics/codes.js +42 -0
  13. package/src/diagnostics/reporter.js +20 -0
  14. package/src/diagnostics.js +139 -0
  15. package/src/dispatch-table.js +23 -0
  16. package/src/entities/catalog.d.ts +7 -0
  17. package/src/entities/catalog.js +87 -0
  18. package/src/entities/contracts.d.ts +95 -0
  19. package/src/entities/contracts.js +122 -0
  20. package/src/entities/json-rpc.d.ts +27 -0
  21. package/src/entities/json-rpc.js +69 -0
  22. package/src/entities/runtime.d.ts +67 -0
  23. package/src/entities/runtime.js +288 -0
  24. package/src/events/contracts.js +456 -0
  25. package/src/execution-plan.js +42 -0
  26. package/src/grip/contracts.js +106 -0
  27. package/src/handler/backend-json-request-body.js +58 -0
  28. package/src/handler/beta-lifecycle-golden.js +97 -0
  29. package/src/handler/canonical-native-plan.js +143 -0
  30. package/src/handler/canonical-native-runtime.js +168 -0
  31. package/src/handler/canonical-runtime.js +13 -0
  32. package/src/handler/context.js +131 -0
  33. package/src/handler/ctx-contract.js +71 -0
  34. package/src/handler/doctor-readiness-aggregator.js +67 -0
  35. package/src/handler/effects.js +446 -0
  36. package/src/handler/io-lifecycle.js +134 -0
  37. package/src/handler/lifecycle-authoring-alignment.js +56 -0
  38. package/src/handler/lifecycle-readiness-aggregate.js +93 -0
  39. package/src/handler/reference-node-lifecycle-golden.js +73 -0
  40. package/src/handler/request-json-body.js +147 -0
  41. package/src/handler/runtime-api.js +471 -0
  42. package/src/handler/schema-decode-result.js +135 -0
  43. package/src/handler/schema-response-codec.js +141 -0
  44. package/src/handler/surface-contract.js +601 -0
  45. package/src/handler/types-contract.js +51 -0
  46. package/src/handler-table.js +28 -0
  47. package/src/host/backend-capabilities.js +24 -0
  48. package/src/host/capabilities.js +273 -0
  49. package/src/host/channel-broadcaster.js +60 -0
  50. package/src/host/json-body.js +80 -0
  51. package/src/host/request-result-headers.js +38 -0
  52. package/src/host/streaming-passthrough.js +90 -0
  53. package/src/host/wasm-host-abi.js +154 -0
  54. package/src/index.js +75 -0
  55. package/src/jwt/contracts.js +427 -0
  56. package/src/library/api-surface.js +99 -0
  57. package/src/library/contracts.js +273 -0
  58. package/src/library/manifest.js +317 -0
  59. package/src/library/second-package-owned-lowering.js +39 -0
  60. package/src/library/sidecars.js +147 -0
  61. package/src/logging.js +89 -0
  62. package/src/package/package-contract.js +975 -0
  63. package/src/package/package-contract.schema.json +303 -0
  64. package/src/package/runtime-pulse-live.js +100 -0
  65. package/src/path-table.js +12 -0
  66. package/src/path.js +275 -0
  67. package/src/project/config-factory.js +48 -0
  68. package/src/project/config-plan.js +409 -0
  69. package/src/project/config-runtime.js +46 -0
  70. package/src/project/javascript-application.js +212 -0
  71. package/src/project/javascript-core-capabilities.js +44 -0
  72. package/src/project/native-optimization.js +9 -0
  73. package/src/project/package-ownership.js +242 -0
  74. package/src/project/package-product-projection.js +171 -0
  75. package/src/project/reachable-graph-identity.js +219 -0
  76. package/src/project/reachable-graph-projections.js +279 -0
  77. package/src/project/reachable-graph-resolver.js +303 -0
  78. package/src/project/reachable-graph-v2.js +430 -0
  79. package/src/project/reachable-graph-v2.schema.json +285 -0
  80. package/src/project/reachable-graph.js +199 -0
  81. package/src/project/reachable-graph.schema.json +222 -0
  82. package/src/project/target-support-evidence.js +559 -0
  83. package/src/provider/canonical-provider.js +239 -0
  84. package/src/provider/final-wasm-policy.js +87 -0
  85. package/src/provider/toolchain.js +771 -0
  86. package/src/route-plan.js +43 -0
  87. package/src/s3/contracts.js +12 -0
  88. package/src/schema-json/compile.js +63 -0
  89. package/src/schema-json/contracts.js +10 -0
  90. package/src/schema-json/generic-parser.js +62 -0
  91. package/src/schema-json/registry.js +359 -0
  92. package/src/schema-json/semantic-trace.js +241 -0
  93. package/src/schema-json/v1.js +65 -0
  94. package/src/schema-json/v2.js +110 -0
  95. package/src/sha256.js +104 -0
  96. package/src/spine.js +381 -0
  97. package/src/stable-id.js +142 -0
package/src/spine.js ADDED
@@ -0,0 +1,381 @@
1
+ 'use strict';
2
+
3
+ const COMPILER_SPINE_CONTRACT_VERSION = 'pulse.compiler-spine.v1';
4
+ const COMPILER_SPINE_CONTRACT_ID = 'pulse.compiler-spine';
5
+
6
+ const COMPILER_RULE_KINDS = Object.freeze([
7
+ 'root-recognizer',
8
+ 'surface-recognizer',
9
+ 'surface-normalizer',
10
+ 'semantic-classifier',
11
+ 'semantic-validator',
12
+ 'canonical-ir-builder',
13
+ 'native-lowerer',
14
+ 'package-lowerer-adapter',
15
+ 'native-realizer',
16
+ 'guest-realizer',
17
+ 'provider-realizer',
18
+ 'artifact-verifier'
19
+ ]);
20
+
21
+ const COMPILER_SPINE_PHASES = Object.freeze([
22
+ Object.freeze({
23
+ id: 'root-extraction',
24
+ order: 10,
25
+ input: 'typescript-source-graph',
26
+ output: 'application-root-facts',
27
+ ruleKinds: Object.freeze(['root-recognizer']),
28
+ ownsTraversal: true,
29
+ mutatesSourceAst: false
30
+ }),
31
+ Object.freeze({
32
+ id: 'surface-recognition',
33
+ order: 20,
34
+ input: 'application-root-facts',
35
+ output: 'recognized-authoring-surfaces',
36
+ ruleKinds: Object.freeze(['surface-recognizer']),
37
+ ownsTraversal: true,
38
+ mutatesSourceAst: false
39
+ }),
40
+ Object.freeze({
41
+ id: 'surface-normalization',
42
+ order: 30,
43
+ input: 'recognized-authoring-surfaces',
44
+ output: 'normalized-handler-operations',
45
+ ruleKinds: Object.freeze(['surface-normalizer']),
46
+ ownsTraversal: false,
47
+ mutatesSourceAst: false
48
+ }),
49
+ Object.freeze({
50
+ id: 'semantic-classification',
51
+ order: 40,
52
+ input: 'normalized-handler-operations',
53
+ output: 'classified-handler-operations',
54
+ ruleKinds: Object.freeze(['semantic-classifier']),
55
+ ownsTraversal: false,
56
+ mutatesSourceAst: false
57
+ }),
58
+ Object.freeze({
59
+ id: 'contract-validation',
60
+ order: 50,
61
+ input: 'classified-handler-operations',
62
+ output: 'validated-handler-program',
63
+ ruleKinds: Object.freeze(['semantic-validator']),
64
+ ownsTraversal: false,
65
+ mutatesSourceAst: false
66
+ }),
67
+ Object.freeze({
68
+ id: 'canonical-ir',
69
+ order: 60,
70
+ input: 'validated-handler-program',
71
+ output: 'application-ir',
72
+ ruleKinds: Object.freeze(['canonical-ir-builder']),
73
+ ownsTraversal: false,
74
+ mutatesSourceAst: false
75
+ }),
76
+ Object.freeze({
77
+ id: 'native-lowering',
78
+ order: 70,
79
+ input: 'application-ir',
80
+ output: 'native-plan',
81
+ ruleKinds: Object.freeze(['native-lowerer']),
82
+ ownsTraversal: false,
83
+ mutatesSourceAst: false
84
+ }),
85
+ Object.freeze({
86
+ id: 'package-lowering',
87
+ order: 80,
88
+ input: 'native-plan-package-operations',
89
+ output: 'package-lowered-native-plan',
90
+ ruleKinds: Object.freeze(['package-lowerer-adapter']),
91
+ ownsTraversal: false,
92
+ mutatesSourceAst: false
93
+ }),
94
+ Object.freeze({
95
+ id: 'native-realization',
96
+ order: 90,
97
+ input: 'package-lowered-native-plan',
98
+ output: 'primary-native-wasm',
99
+ ruleKinds: Object.freeze(['native-realizer']),
100
+ ownsTraversal: false,
101
+ mutatesSourceAst: false
102
+ }),
103
+ Object.freeze({
104
+ id: 'guest-realization',
105
+ order: 100,
106
+ input: 'primary-native-wasm',
107
+ output: 'composed-native-wasm',
108
+ ruleKinds: Object.freeze(['guest-realizer']),
109
+ ownsTraversal: false,
110
+ mutatesSourceAst: false
111
+ }),
112
+ Object.freeze({
113
+ id: 'artifact-verification',
114
+ order: 110,
115
+ input: 'composed-native-wasm',
116
+ output: 'verified-build-evidence',
117
+ ruleKinds: Object.freeze(['artifact-verifier']),
118
+ ownsTraversal: false,
119
+ mutatesSourceAst: false
120
+ }),
121
+ Object.freeze({
122
+ id: 'provider-realization',
123
+ order: 120,
124
+ input: 'verified-build-evidence',
125
+ output: 'provider-artifacts',
126
+ ruleKinds: Object.freeze(['provider-realizer']),
127
+ ownsTraversal: false,
128
+ mutatesSourceAst: false
129
+ })
130
+ ]);
131
+
132
+ const COMPILER_SPINE_POLICY = Object.freeze({
133
+ internalOnly: true,
134
+ publicPluginApi: false,
135
+ externalRuleRegistration: false,
136
+ dynamicRuleRegistration: false,
137
+ runtimeRuleDiscovery: false,
138
+ phaseOrdering: 'fixed-and-versioned',
139
+ traversalOwnership: 'phase-owned',
140
+ sharedMutableCompilerContext: false,
141
+ sourceAstMutation: false,
142
+ canonicalIrPaths: 1,
143
+ packageLoweringInput: 'canonical-package-operation-only',
144
+ packageLowererTrust: 'manifest-declared-first-party',
145
+ providerRealizationInput: 'provider-neutral-native-plan',
146
+ behaviorChangeInContractFreeze: false
147
+ });
148
+
149
+ const DEFAULT_COMPILER_SPINE_RULES = Object.freeze([
150
+ Object.freeze({
151
+ id: 'pulse.root.plain-handler',
152
+ phase: 'root-extraction',
153
+ kind: 'root-recognizer',
154
+ owner: 'pulse.compiler-orchestration',
155
+ status: 'existing',
156
+ priority: 10,
157
+ dependsOn: Object.freeze([])
158
+ }),
159
+ Object.freeze({
160
+ id: 'pulse.root.router',
161
+ phase: 'root-extraction',
162
+ kind: 'root-recognizer',
163
+ owner: 'pulse.compiler-orchestration',
164
+ status: 'existing',
165
+ priority: 20,
166
+ dependsOn: Object.freeze([])
167
+ }),
168
+ Object.freeze({
169
+ id: 'pulse.root.pulse',
170
+ phase: 'root-extraction',
171
+ kind: 'root-recognizer',
172
+ owner: 'pulse.compiler-orchestration',
173
+ status: 'planned',
174
+ priority: 30,
175
+ dependsOn: Object.freeze([])
176
+ }),
177
+ Object.freeze({
178
+ id: 'pulse.surface.ctx',
179
+ phase: 'surface-recognition',
180
+ kind: 'surface-recognizer',
181
+ owner: 'pulse.compiler-orchestration',
182
+ status: 'contract-frozen',
183
+ priority: 10,
184
+ dependsOn: Object.freeze(['pulse.root.plain-handler', 'pulse.root.router', 'pulse.root.pulse'])
185
+ }),
186
+ Object.freeze({
187
+ id: 'pulse.surface.authoring-sugar',
188
+ phase: 'surface-normalization',
189
+ kind: 'surface-normalizer',
190
+ owner: 'pulse.compiler-orchestration',
191
+ status: 'planned',
192
+ priority: 10,
193
+ dependsOn: Object.freeze(['pulse.surface.ctx'])
194
+ }),
195
+ Object.freeze({
196
+ id: 'pulse.semantic.handler-surface',
197
+ phase: 'semantic-classification',
198
+ kind: 'semantic-classifier',
199
+ owner: '@pulse-compute/wasm-contracts',
200
+ status: 'contract-frozen',
201
+ priority: 10,
202
+ dependsOn: Object.freeze(['pulse.surface.authoring-sugar'])
203
+ }),
204
+ Object.freeze({
205
+ id: 'pulse.validate.handler-contract',
206
+ phase: 'contract-validation',
207
+ kind: 'semantic-validator',
208
+ owner: 'pulse.compiler-orchestration',
209
+ status: 'existing-to-converge',
210
+ priority: 10,
211
+ dependsOn: Object.freeze(['pulse.semantic.handler-surface'])
212
+ }),
213
+ Object.freeze({
214
+ id: 'pulse.ir.application',
215
+ phase: 'canonical-ir',
216
+ kind: 'canonical-ir-builder',
217
+ owner: 'pulse.compiler-orchestration',
218
+ status: 'existing-to-envelope',
219
+ priority: 10,
220
+ dependsOn: Object.freeze(['pulse.validate.handler-contract'])
221
+ }),
222
+ Object.freeze({
223
+ id: 'pulse.lower.native-effects',
224
+ phase: 'native-lowering',
225
+ kind: 'native-lowerer',
226
+ owner: 'pulse.compiler-orchestration',
227
+ status: 'existing',
228
+ priority: 10,
229
+ dependsOn: Object.freeze(['pulse.ir.application'])
230
+ }),
231
+ Object.freeze({
232
+ id: 'pulse.lower.package-contracts',
233
+ phase: 'package-lowering',
234
+ kind: 'package-lowerer-adapter',
235
+ owner: 'pulse.package-lowering',
236
+ status: 'existing',
237
+ priority: 10,
238
+ dependsOn: Object.freeze(['pulse.lower.native-effects'])
239
+ }),
240
+ Object.freeze({
241
+ id: 'pulse.realize.native-primary',
242
+ phase: 'native-realization',
243
+ kind: 'native-realizer',
244
+ owner: 'pulse.compiler-orchestration',
245
+ status: 'existing',
246
+ priority: 10,
247
+ dependsOn: Object.freeze(['pulse.lower.package-contracts'])
248
+ }),
249
+ Object.freeze({
250
+ id: 'pulse.realize.guest-units',
251
+ phase: 'guest-realization',
252
+ kind: 'guest-realizer',
253
+ owner: '@pulse-compute/wasm-guest-link',
254
+ status: 'implemented',
255
+ priority: 10,
256
+ dependsOn: Object.freeze(['pulse.realize.native-primary'])
257
+ }),
258
+ Object.freeze({
259
+ id: 'pulse.verify.final-wasm',
260
+ phase: 'artifact-verification',
261
+ kind: 'artifact-verifier',
262
+ owner: 'pulse.artifact-tooling',
263
+ status: 'existing-to-converge',
264
+ priority: 10,
265
+ dependsOn: Object.freeze(['pulse.realize.guest-units'])
266
+ }),
267
+ Object.freeze({
268
+ id: 'pulse.realize.provider',
269
+ phase: 'provider-realization',
270
+ kind: 'provider-realizer',
271
+ owner: 'pulse.selected-provider',
272
+ status: 'existing',
273
+ priority: 10,
274
+ dependsOn: Object.freeze(['pulse.verify.final-wasm'])
275
+ })
276
+ ]);
277
+
278
+ function isNonEmptyString(value) {
279
+ return typeof value === 'string' && value.trim().length > 0;
280
+ }
281
+
282
+ function phaseMap() {
283
+ return new Map(COMPILER_SPINE_PHASES.map((phase) => [phase.id, phase]));
284
+ }
285
+
286
+ function validateRule(rule, phases) {
287
+ if (!rule || typeof rule !== 'object') throw new TypeError('Compiler spine rule must be an object.');
288
+ if (!isNonEmptyString(rule.id)) throw new TypeError('Compiler spine rule requires a stable id.');
289
+ if (!isNonEmptyString(rule.phase) || !phases.has(rule.phase)) throw new TypeError(`Compiler spine rule ${rule.id} references an unknown phase.`);
290
+ if (!COMPILER_RULE_KINDS.includes(rule.kind)) throw new TypeError(`Compiler spine rule ${rule.id} uses unsupported kind ${rule.kind}.`);
291
+ const phase = phases.get(rule.phase);
292
+ if (!phase.ruleKinds.includes(rule.kind)) throw new TypeError(`Compiler spine rule ${rule.id} kind ${rule.kind} is not allowed in phase ${rule.phase}.`);
293
+ if (!isNonEmptyString(rule.owner)) throw new TypeError(`Compiler spine rule ${rule.id} requires an owner.`);
294
+ if (!Array.isArray(rule.dependsOn)) throw new TypeError(`Compiler spine rule ${rule.id} requires a dependsOn array.`);
295
+ }
296
+
297
+ function assertAcyclic(byId) {
298
+ const visiting = new Set();
299
+ const visited = new Set();
300
+
301
+ function visit(id, stack) {
302
+ if (visited.has(id)) return;
303
+ if (visiting.has(id)) throw new TypeError(`Compiler spine rule dependency cycle: ${[...stack, id].join(' -> ')}`);
304
+ const rule = byId.get(id);
305
+ if (!rule) throw new TypeError(`Compiler spine rule dependency is unknown: ${id}`);
306
+ visiting.add(id);
307
+ for (const dependency of rule.dependsOn) visit(dependency, [...stack, id]);
308
+ visiting.delete(id);
309
+ visited.add(id);
310
+ }
311
+
312
+ for (const id of byId.keys()) visit(id, []);
313
+ }
314
+
315
+ function buildCompilerSpineRegistry(rules = DEFAULT_COMPILER_SPINE_RULES) {
316
+ const phases = phaseMap();
317
+ const byId = new Map();
318
+ for (const rule of rules) {
319
+ validateRule(rule, phases);
320
+ if (byId.has(rule.id)) throw new TypeError(`Duplicate compiler spine rule id: ${rule.id}`);
321
+ byId.set(rule.id, rule);
322
+ }
323
+ for (const rule of byId.values()) {
324
+ for (const dependency of rule.dependsOn) {
325
+ if (!byId.has(dependency)) throw new TypeError(`Compiler spine rule ${rule.id} depends on unknown rule ${dependency}.`);
326
+ const dependencyPhase = phases.get(byId.get(dependency).phase);
327
+ const rulePhase = phases.get(rule.phase);
328
+ if (dependencyPhase.order > rulePhase.order) {
329
+ throw new TypeError(`Compiler spine rule ${rule.id} depends on later phase rule ${dependency}.`);
330
+ }
331
+ }
332
+ }
333
+ assertAcyclic(byId);
334
+
335
+ const ordered = Array.from(byId.values()).sort((left, right) => {
336
+ const phaseOrder = phases.get(left.phase).order - phases.get(right.phase).order;
337
+ if (phaseOrder !== 0) return phaseOrder;
338
+ const priorityOrder = Number(left.priority || 0) - Number(right.priority || 0);
339
+ if (priorityOrder !== 0) return priorityOrder;
340
+ return left.id.localeCompare(right.id);
341
+ });
342
+
343
+ return Object.freeze({
344
+ version: COMPILER_SPINE_CONTRACT_VERSION,
345
+ contractId: COMPILER_SPINE_CONTRACT_ID,
346
+ phases: COMPILER_SPINE_PHASES,
347
+ policy: COMPILER_SPINE_POLICY,
348
+ rules: Object.freeze(ordered),
349
+ get(id) {
350
+ return byId.get(id);
351
+ },
352
+ rulesForPhase(phaseId) {
353
+ if (!phases.has(phaseId)) throw new TypeError(`Unknown compiler spine phase: ${phaseId}`);
354
+ return Object.freeze(ordered.filter((rule) => rule.phase === phaseId));
355
+ }
356
+ });
357
+ }
358
+
359
+ const DEFAULT_COMPILER_SPINE_REGISTRY = buildCompilerSpineRegistry();
360
+
361
+ function defaultCompilerSpineContract() {
362
+ return Object.freeze({
363
+ version: COMPILER_SPINE_CONTRACT_VERSION,
364
+ contractId: COMPILER_SPINE_CONTRACT_ID,
365
+ phases: COMPILER_SPINE_PHASES,
366
+ policy: COMPILER_SPINE_POLICY,
367
+ rules: DEFAULT_COMPILER_SPINE_REGISTRY.rules
368
+ });
369
+ }
370
+
371
+ module.exports = {
372
+ COMPILER_SPINE_CONTRACT_VERSION,
373
+ COMPILER_SPINE_CONTRACT_ID,
374
+ COMPILER_RULE_KINDS,
375
+ COMPILER_SPINE_PHASES,
376
+ COMPILER_SPINE_POLICY,
377
+ DEFAULT_COMPILER_SPINE_RULES,
378
+ DEFAULT_COMPILER_SPINE_REGISTRY,
379
+ buildCompilerSpineRegistry,
380
+ defaultCompilerSpineContract
381
+ };
@@ -0,0 +1,142 @@
1
+ 'use strict';
2
+
3
+ const { sha256Hex: portableSha256Hex } = require('./sha256.js');
4
+ const { HANDLER_STABLE_INPUT_VERSION } = require('./handler-table.js');
5
+ const { ROUTE_STABLE_INPUT_VERSION } = require('./route-plan.js');
6
+
7
+ function canonicalize(value) {
8
+ if (Array.isArray(value)) return value.map(canonicalize);
9
+ if (!value || typeof value !== 'object') return value;
10
+ const out = {};
11
+ for (const key of Object.keys(value).sort()) {
12
+ const child = value[key];
13
+ if (child !== undefined) out[key] = canonicalize(child);
14
+ }
15
+ return out;
16
+ }
17
+
18
+ function stableStringify(value) {
19
+ return JSON.stringify(canonicalize(value));
20
+ }
21
+
22
+ function sha256Hex(value) {
23
+ return portableSha256Hex(String(value));
24
+ }
25
+
26
+ function stableHash(value, options = {}) {
27
+ const hex = sha256Hex(stableStringify(value));
28
+ return hex.slice(0, options.length || 24);
29
+ }
30
+
31
+ function sourceTextHash(sourceText, options = {}) {
32
+ return sha256Hex(String(sourceText || '')).slice(0, options.length || 24);
33
+ }
34
+
35
+ function prefixedStableId(prefix, value, options = {}) {
36
+ return `${prefix}_${stableHash(value, options)}`;
37
+ }
38
+
39
+ function createHandlerStableInput(handlerParts) {
40
+ return {
41
+ artifact: HANDLER_STABLE_INPUT_VERSION,
42
+ file: handlerParts.file,
43
+ kind: handlerParts.kind,
44
+ localName: handlerParts.localName || null,
45
+ exportName: handlerParts.exportName || null,
46
+ inline: Boolean(handlerParts.inline),
47
+ loc: handlerParts.inline ? handlerParts.loc || null : undefined,
48
+ sourceTextHash: handlerParts.sourceTextHash
49
+ };
50
+ }
51
+
52
+ function createHandlerStableId(handlerParts) {
53
+ return prefixedStableId('handler', createHandlerStableInput(handlerParts));
54
+ }
55
+
56
+ function normalizeRefIdentity(ref) {
57
+ if (!ref) return null;
58
+ if (typeof ref === 'string') return { kind: 'legacy-name', value: ref };
59
+ if (ref.kind === 'handler') return { kind: 'handler', id: ref.id };
60
+ if (ref.id && ref.kind !== 'static') return { kind: 'handler', id: ref.id };
61
+ if (ref.kind === 'identifier') return { kind: 'identifier', value: ref.value };
62
+ if (ref.kind === 'inline') {
63
+ // Legacy Phase 2 behavior for pre-handler-table references.
64
+ return { kind: 'inline', value: String(ref.value || '').replace(/\s+/g, ' ').trim() };
65
+ }
66
+ if (ref.kind === 'ref' || ref.kind === 'static') return { kind: ref.kind, value: ref.value };
67
+ return { kind: ref.kind || 'unknown', value: ref.value === undefined ? null : String(ref.value) };
68
+ }
69
+
70
+ function normalizeChannelIdentity(channel) {
71
+ if (!channel) return null;
72
+ return normalizeRefIdentity(channel);
73
+ }
74
+
75
+ function buildRouteStableInput(routeParts) {
76
+ return {
77
+ artifact: routeParts.artifact || ROUTE_STABLE_INPUT_VERSION,
78
+ method: routeParts.method,
79
+ path: routeParts.path,
80
+ params: routeParts.params || [],
81
+ handler: normalizeRefIdentity(routeParts.handler),
82
+ middleware: (routeParts.middleware || []).map(normalizeRefIdentity),
83
+ errorHandlers: (routeParts.errorHandlers || []).map(normalizeRefIdentity),
84
+ connectHandlers: (routeParts.connectHandlers || []).map(normalizeRefIdentity),
85
+ disconnectHandlers: (routeParts.disconnectHandlers || []).map(normalizeRefIdentity),
86
+ channel: normalizeChannelIdentity(routeParts.channel)
87
+ };
88
+ }
89
+
90
+ function createRouteStableId(routeParts) {
91
+ return prefixedStableId('route', buildRouteStableInput(routeParts));
92
+ }
93
+
94
+ function createRouteIdMap(routePlan, options = {}) {
95
+ const generatedBy = options.generatedBy || routePlan.generatedBy;
96
+ const routes = (routePlan.routes || []).map((route) => ({
97
+ stableId: route.stableId,
98
+ runtimeId: route.runtimeId ?? route.id,
99
+ id: route.id,
100
+ method: route.method,
101
+ path: route.path,
102
+ handler: route.handler,
103
+ handlerName: route.handlerName,
104
+ params: route.params || [],
105
+ pathPattern: route.pathPattern,
106
+ sourceRouter: route.sourceRouter,
107
+ order: route.order
108
+ }));
109
+
110
+ return {
111
+ version: 'pulsewasm.route-id-map.v3',
112
+ generatedBy,
113
+ source: routePlan.source,
114
+ entryRouter: routePlan.entryRouter,
115
+ idPolicy: routePlan.idPolicy,
116
+ pathPolicy: routePlan.pathPolicy,
117
+ routes,
118
+ summary: {
119
+ routes: routes.length,
120
+ stableIds: new Set(routes.map((route) => route.stableId)).size,
121
+ runtimeIds: new Set(routes.map((route) => route.runtimeId)).size,
122
+ handlerIds: new Set(routes.map((route) => route.handler).filter(Boolean)).size,
123
+ wildcardRoutes: routes.filter((route) => route.pathPattern && route.pathPattern.wildcard).length
124
+ }
125
+ };
126
+ }
127
+
128
+ module.exports = {
129
+ buildRouteStableInput,
130
+ canonicalize,
131
+ createHandlerStableId,
132
+ createHandlerStableInput,
133
+ createRouteIdMap,
134
+ createRouteStableId,
135
+ normalizeChannelIdentity,
136
+ normalizeRefIdentity,
137
+ prefixedStableId,
138
+ sha256Hex,
139
+ sourceTextHash,
140
+ stableHash,
141
+ stableStringify
142
+ };