@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,500 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ normalizeTargetSupportEvidence
5
+ } = require('@pulse-compute/wasm-contracts/project/target-support-evidence');
6
+ const { sha256Hex, stableStringify } = require('@pulse-compute/wasm-contracts/stable-id');
7
+ const { requirementsFromMetadata } = require('@pulse-compute/wasm-contracts/provider/canonical-provider');
8
+
9
+ const EVENT_TARGET_SUPPORT_VERSION = 'pulse.event-target-support.v1';
10
+
11
+ function decision(id, status, reasonId, owner, required = true) {
12
+ return Object.freeze({ id, status, required, reasonId, owner });
13
+ }
14
+
15
+ function sourceRecords(values) {
16
+ const unique = new Map();
17
+ for (const source of values || []) {
18
+ if (!source || !source.file) continue;
19
+ const normalized = Object.freeze({
20
+ file: String(source.file),
21
+ line: Number.isInteger(source.line) && source.line > 0 ? source.line : 1,
22
+ column: Number.isInteger(source.column) && source.column > 0 ? source.column : 1
23
+ });
24
+ unique.set(`${normalized.file}:${normalized.line}:${normalized.column}`, normalized);
25
+ }
26
+ return Object.freeze([...unique.values()].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column));
27
+ }
28
+
29
+ function sourcesForPackage(reachable, planned) {
30
+ return sourceRecords([
31
+ ...((planned && planned.sources) || []),
32
+ ...((reachable && reachable.importSources) || []),
33
+ ...((reachable && reachable.reExportSources) || [])
34
+ ]);
35
+ }
36
+
37
+ function pendingPackageOwner(product) {
38
+ if (product && product.contractId === 'pulse.assets') return 'assets';
39
+ if (product && product.contractId === 'pulse.grip') return 'grip';
40
+ return 'compiler';
41
+ }
42
+
43
+ function productContext(compiled) {
44
+ const product = compiled.packageProduct || {};
45
+ const contracts = product.contracts || [];
46
+ const byName = new Map(contracts.map((entry) => [entry.packageName, entry]));
47
+ const byId = new Map(contracts.map((entry) => [entry.contractId, entry]));
48
+ const selected = new Set([
49
+ ...((compiled.packageReachability && compiled.packageReachability.selectedContracts) || []),
50
+ ...(product.selectedContracts || []),
51
+ ...(product.composedContracts || [])
52
+ ]);
53
+ const configured = new Set((product.fragments || [])
54
+ .filter((entry) => entry.present && entry.contractId)
55
+ .map((entry) => entry.contractId));
56
+ return Object.freeze({ product, byName, byId, selected, configured });
57
+ }
58
+
59
+ function packageCommon(reachable, planned, product, context) {
60
+ const reachableRecord = Boolean(reachable || planned);
61
+ const runtime = Boolean(reachable && reachable.runtime === true) || Boolean(planned);
62
+ const contractId = reachable && reachable.contractId || planned && planned.contractId || product && product.contractId || null;
63
+ const selected = Boolean(contractId && context.selected.has(contractId));
64
+ const configured = Boolean(contractId && context.configured.has(contractId));
65
+ return {
66
+ moduleId: reachable && reachable.moduleId || planned && planned.moduleId || `contract:${contractId || product && product.packageName || 'unknown'}`,
67
+ packageName: reachable && reachable.packageName || planned && planned.packageName || product && product.packageName,
68
+ packageSubpath: reachable && reachable.packageSubpath || planned && planned.packageSubpath || '.',
69
+ contractId,
70
+ reachable: reachableRecord,
71
+ runtime,
72
+ selected,
73
+ configured,
74
+ required: runtime || selected || configured,
75
+ reasonCode: planned && planned.reasonCode || product && product.javascriptTarget && product.javascriptTarget.reasonCode || null,
76
+ realization: planned && planned.status || product && product.javascriptTarget && product.javascriptTarget.realization || null,
77
+ targetStatus: product && product.javascriptTarget && product.javascriptTarget.status || null,
78
+ runtimeEntry: planned && planned.runtimeEntry || product && product.javascriptTarget && product.javascriptTarget.entry || null,
79
+ sources: sourcesForPackage(reachable, planned)
80
+ };
81
+ }
82
+
83
+ function classifyPackage(reachable, planned, product, context, options = {}) {
84
+ const common = packageCommon(reachable, planned, product, context);
85
+ if (!common.required) {
86
+ return Object.freeze({
87
+ ...common,
88
+ status: 'irrelevant',
89
+ reasonId: common.reachable ? 'type-only-or-nonparticipating-package' : 'package-contract-not-selected',
90
+ owner: common.reachable ? 'reachable-graph' : 'package-product'
91
+ });
92
+ }
93
+
94
+ const targetStatus = product && product.javascriptTarget && product.javascriptTarget.status;
95
+ if (targetStatus === 'not-realized' || targetStatus === 'provider-dependent') {
96
+ return Object.freeze({ ...common, status: 'pending', reasonId: 'package-javascript-realization-pending', owner: pendingPackageOwner(product) });
97
+ }
98
+ if (targetStatus === 'unsupported') {
99
+ return Object.freeze({ ...common, status: 'blocked', reasonId: 'package-javascript-target-unsupported', owner: product.contractId });
100
+ }
101
+
102
+ if (!planned) {
103
+ if (common.runtime) {
104
+ return Object.freeze({ ...common, status: 'blocked', reasonId: 'runtime-package-missing-from-javascript-plan', owner: 'compiler' });
105
+ }
106
+ if (product && targetStatus === 'supported') {
107
+ return Object.freeze({ ...common, status: 'eligible', reasonId: 'selected-package-javascript-target-supported', owner: product.contractId });
108
+ }
109
+ return Object.freeze({ ...common, status: 'blocked', reasonId: 'selected-package-realization-unclassified', owner: product && product.contractId || 'compiler' });
110
+ }
111
+ if (planned.status === 'supplied-core') {
112
+ return Object.freeze({
113
+ ...common,
114
+ status: 'eligible',
115
+ reasonId: 'core-javascript-package-supplied',
116
+ owner: options.suppliedCoreOwner || 'provider-node'
117
+ });
118
+ }
119
+ if (planned.status === 'node-require') {
120
+ return Object.freeze({
121
+ ...common,
122
+ status: 'eligible',
123
+ reasonId: options.packageResolutionReasonId || 'node-package-resolution-planned',
124
+ owner: product && product.contractId || options.packageResolutionOwner || 'provider-node'
125
+ });
126
+ }
127
+ if (planned.status === 'package-runtime') {
128
+ if (!planned.runtimeEntry) {
129
+ return Object.freeze({ ...common, status: 'blocked', reasonId: 'package-javascript-runtime-entry-missing', owner: product && product.contractId || 'compiler' });
130
+ }
131
+ return Object.freeze({
132
+ ...common,
133
+ status: 'eligible',
134
+ reasonId: 'package-javascript-runtime-planned',
135
+ owner: product && product.contractId || options.packageRuntimeOwner || 'provider-node'
136
+ });
137
+ }
138
+ if (planned.status === 'unavailable') {
139
+ return Object.freeze({ ...common, status: 'blocked', reasonId: 'package-javascript-realization-unclassified', owner: product && product.contractId || 'compiler' });
140
+ }
141
+ return Object.freeze({ ...common, status: 'blocked', reasonId: 'package-javascript-plan-status-unknown', owner: 'compiler' });
142
+ }
143
+
144
+ function packageEligibility(compiled, javascriptApplication, options = {}) {
145
+ const context = productContext(compiled);
146
+ const reachablePackages = compiled.packageReachability && compiled.packageReachability.packages || [];
147
+ const plannedPackages = javascriptApplication.plan.packages || [];
148
+ const plannedByModule = new Map(plannedPackages.map((entry) => [entry.moduleId, entry]));
149
+ const coveredModules = new Set();
150
+ const coveredContracts = new Set();
151
+ const records = [];
152
+
153
+ for (const reachable of reachablePackages) {
154
+ const planned = plannedByModule.get(reachable.moduleId) || null;
155
+ const product = context.byName.get(reachable.packageName) || (reachable.contractId && context.byId.get(reachable.contractId)) || null;
156
+ records.push(classifyPackage(reachable, planned, product, context, options));
157
+ coveredModules.add(reachable.moduleId);
158
+ if (product) coveredContracts.add(product.contractId);
159
+ }
160
+
161
+ for (const planned of plannedPackages) {
162
+ if (coveredModules.has(planned.moduleId)) continue;
163
+ const product = context.byName.get(planned.packageName) || (planned.contractId && context.byId.get(planned.contractId)) || null;
164
+ records.push(classifyPackage(null, planned, product, context, options));
165
+ if (product) coveredContracts.add(product.contractId);
166
+ }
167
+
168
+ for (const product of context.product.contracts || []) {
169
+ if (coveredContracts.has(product.contractId)) continue;
170
+ records.push(classifyPackage(null, null, product, context, options));
171
+ }
172
+
173
+ return Object.freeze(records.sort((a, b) => a.packageName.localeCompare(b.packageName) || a.packageSubpath.localeCompare(b.packageSubpath) || a.moduleId.localeCompare(b.moduleId)));
174
+ }
175
+
176
+ function applicationPlanDecision(javascriptApplication, packages) {
177
+ const requiredPackages = packages.filter((entry) => entry.required);
178
+ if (javascriptApplication.plan.loadable === true) {
179
+ return decision('application.plan', 'eligible', 'javascript-application-plan-loadable', 'compiler');
180
+ }
181
+ const blockers = javascriptApplication.plan.blockers || [];
182
+ const packageOnlyBlockers = blockers.length > 0 && blockers.every((entry) => entry.kind === 'javascript-package-realization-unavailable');
183
+ if (packageOnlyBlockers) {
184
+ if (requiredPackages.some((entry) => entry.status === 'blocked')) {
185
+ return decision('application.plan', 'blocked', 'javascript-application-package-blocked', 'compiler');
186
+ }
187
+ if (requiredPackages.some((entry) => entry.status === 'pending')) {
188
+ return decision('application.plan', 'pending', 'javascript-application-packages-pending', 'compiler');
189
+ }
190
+ }
191
+ return decision('application.plan', 'blocked', 'javascript-application-plan-blocked', 'project');
192
+ }
193
+
194
+ function entrySafetyDecision(javascriptApplication) {
195
+ const entrySafety = javascriptApplication.plan.entrySafety;
196
+ return entrySafety && entrySafety.importSafe === false
197
+ ? decision('application.entry-safety', 'blocked', 'application-entry-lifecycle-side-effect', 'project')
198
+ : decision('application.entry-safety', 'eligible', 'application-entry-import-safe', 'reachable-graph');
199
+ }
200
+
201
+ function expectedProjectStatus(application, capabilities, providerRequirements, packages) {
202
+ const required = [
203
+ ...application.filter((entry) => entry.required),
204
+ ...capabilities.filter((entry) => entry.required),
205
+ ...providerRequirements.filter((entry) => entry.required),
206
+ ...packages.filter((entry) => entry.required)
207
+ ];
208
+ if (required.some((entry) => entry.status === 'blocked')) return 'blocked';
209
+ if (required.some((entry) => entry.status === 'pending')) return 'pending';
210
+ return 'eligible';
211
+ }
212
+
213
+ function reasonIdsFor(application, capabilities, providerRequirements, packages) {
214
+ return Object.freeze([...new Set([
215
+ ...application.filter((entry) => entry.required && entry.status !== 'eligible'),
216
+ ...capabilities.filter((entry) => entry.required && entry.status !== 'eligible'),
217
+ ...providerRequirements.filter((entry) => entry.required && entry.status !== 'eligible'),
218
+ ...packages.filter((entry) => entry.required && entry.status !== 'eligible')
219
+ ].map((entry) => entry.reasonId))].sort());
220
+ }
221
+
222
+ function assertDescriptorTruth(descriptor, declaration) {
223
+ if (descriptor.targetId !== declaration.targetId || descriptor.provider !== declaration.provider || descriptor.target !== declaration.target) {
224
+ throw new TypeError('JavaScript target descriptor and support declaration identify different targets.');
225
+ }
226
+ for (const command of declaration.commands) {
227
+ const implemented = Boolean(descriptor.commands && descriptor.commands[command.id]);
228
+ if (implemented !== command.implemented) throw new TypeError(`JavaScript command truth diverged for ${command.id}.`);
229
+ }
230
+ }
231
+
232
+ function decisionSortKey(entry) {
233
+ return entry.id || `package:${entry.packageName}:${entry.packageSubpath}:${entry.moduleId}`;
234
+ }
235
+
236
+ function firstDecisionAtStatus(status, application, capabilities, providerRequirements, packages) {
237
+ return [...application, ...capabilities, ...providerRequirements, ...packages]
238
+ .filter((entry) => entry.required && entry.status === status)
239
+ .sort((a, b) => decisionSortKey(a).localeCompare(decisionSortKey(b)))[0] || null;
240
+ }
241
+
242
+ function projectCommands(declaration, status, nativeEligibility, application, capabilities, providerRequirements, packages, options = {}) {
243
+ const projectDecision = firstDecisionAtStatus(status, application, capabilities, providerRequirements, packages);
244
+ return Object.freeze(declaration.commands.map((command) => {
245
+ if (command.status !== 'eligible') return command;
246
+ if (command.id === 'test' || command.id === 'dev') {
247
+ return Object.freeze({
248
+ id: command.id,
249
+ implemented: true,
250
+ status,
251
+ reasonId: status === 'eligible' ? command.reasonId : projectDecision.reasonId,
252
+ owner: status === 'eligible' ? command.owner : projectDecision.owner
253
+ });
254
+ }
255
+ if (command.id === 'build' && options.projectScopedBuild === true && status !== 'eligible') {
256
+ return Object.freeze({
257
+ id: command.id,
258
+ implemented: true,
259
+ status,
260
+ reasonId: projectDecision.reasonId,
261
+ owner: projectDecision.owner
262
+ });
263
+ }
264
+ if (command.id === 'compile' && nativeEligibility.eligible !== true) {
265
+ return Object.freeze({
266
+ id: command.id,
267
+ implemented: true,
268
+ status: 'blocked',
269
+ reasonId: 'native-eligibility-blocked',
270
+ owner: 'native-eligibility'
271
+ });
272
+ }
273
+ return command;
274
+ }));
275
+ }
276
+
277
+ function loaderObservation(javascriptApplication) {
278
+ if (javascriptApplication.loaded) {
279
+ return Object.freeze({
280
+ version: javascriptApplication.loaded.version,
281
+ attempted: true,
282
+ loaded: true,
283
+ modulesLoaded: javascriptApplication.loaded.modulesLoaded,
284
+ packagesLoaded: javascriptApplication.loaded.packagesLoaded,
285
+ errorCode: null,
286
+ skippedReason: null
287
+ });
288
+ }
289
+ return Object.freeze({
290
+ version: null,
291
+ attempted: javascriptApplication.loadAttempted === true,
292
+ loaded: false,
293
+ modulesLoaded: 0,
294
+ packagesLoaded: [],
295
+ errorCode: javascriptApplication.loadError && javascriptApplication.loadError.code
296
+ || (javascriptApplication.loadAttempted === true ? 'PULSE_JAVASCRIPT_APPLICATION_LOAD_FAILED' : null),
297
+ skippedReason: javascriptApplication.loadAttempted === true
298
+ ? null
299
+ : javascriptApplication.loadSkippedReason || 'application-load-not-requested'
300
+ });
301
+ }
302
+
303
+ function buildJavascriptTargetSupportEvidence(project, options = {}, policy = {}) {
304
+ const compiled = options.compiled;
305
+ const javascriptApplication = options.javascriptApplication;
306
+ const descriptor = options.descriptor || javascriptApplication && javascriptApplication.descriptor;
307
+ const declaration = options.declaration || policy.declaration;
308
+ const providerId = policy.providerId || declaration && declaration.provider;
309
+ const label = providerId ? `${providerId} JavaScript` : 'JavaScript';
310
+ if (!compiled || !compiled.metadata) throw new TypeError(`${label} target support evidence requires compiled project metadata.`);
311
+ if (!compiled.nativeEligibility) throw new TypeError(`${label} target support evidence requires the native eligibility projection.`);
312
+ if (!javascriptApplication || !javascriptApplication.plan) throw new TypeError(`${label} target support evidence requires a JavaScript application plan.`);
313
+ if (!descriptor) throw new TypeError(`${label} target support evidence requires the selected target descriptor.`);
314
+ if (!declaration) throw new TypeError(`${label} target support evidence requires a support declaration.`);
315
+ if (typeof policy.classifyCapability !== 'function' || typeof policy.classifyProviderRequirement !== 'function') {
316
+ throw new TypeError(`${label} target support evidence requires provider-owned classification policy.`);
317
+ }
318
+ if (project.provider !== providerId || (project.target || 'native') !== 'javascript') {
319
+ throw new TypeError(`${label} target support evidence requires a ${providerId}/javascript project selection.`);
320
+ }
321
+
322
+ assertDescriptorTruth(descriptor, declaration);
323
+ const context = typeof policy.context === 'function'
324
+ ? policy.context(compiled, project, declaration)
325
+ : Object.freeze({});
326
+ const compilerCapabilities = Object.freeze([...(compiled.metadata.capabilities || [])].map(String).sort());
327
+ let packages = packageEligibility(compiled, javascriptApplication, policy.packageOptions || {});
328
+ if (typeof policy.adjustPackages === 'function') {
329
+ packages = Object.freeze(policy.adjustPackages(packages, context, compiled, project));
330
+ }
331
+ const application = Object.freeze([
332
+ entrySafetyDecision(javascriptApplication),
333
+ applicationPlanDecision(javascriptApplication, packages)
334
+ ].sort((a, b) => a.id.localeCompare(b.id)));
335
+ const capabilities = Object.freeze(compilerCapabilities
336
+ .map((id) => policy.classifyCapability(id, context))
337
+ .sort((a, b) => a.id.localeCompare(b.id)));
338
+ const providerRequirements = Object.freeze(requirementsFromMetadata(compiled.metadata)
339
+ .map((id) => policy.classifyProviderRequirement(id, compilerCapabilities, context))
340
+ .sort((a, b) => a.id.localeCompare(b.id)));
341
+ const status = expectedProjectStatus(application, capabilities, providerRequirements, packages);
342
+ const reasonIds = reasonIdsFor(application, capabilities, providerRequirements, packages);
343
+ const commands = projectCommands(
344
+ declaration,
345
+ status,
346
+ compiled.nativeEligibility,
347
+ application,
348
+ capabilities,
349
+ providerRequirements,
350
+ packages,
351
+ policy.commandOptions || {}
352
+ );
353
+
354
+ return normalizeTargetSupportEvidence({
355
+ declaration,
356
+ descriptor: {
357
+ version: descriptor.version || 'pulse.javascript-target-descriptor.v2',
358
+ status: descriptor.status,
359
+ commands: descriptor.commands,
360
+ descriptorHash: sha256Hex(stableStringify(descriptor))
361
+ },
362
+ project: {
363
+ status,
364
+ reasonIds,
365
+ projectHash: project.projectHash || null,
366
+ configPlanHash: project.planHash || null,
367
+ sourceHash: compiled.metadata.sourceHash,
368
+ graphHash: javascriptApplication.plan.graph.graphHash,
369
+ applicationPlanHash: javascriptApplication.plan.planHash,
370
+ packageProjectionHash: compiled.packageReachability && compiled.packageReachability.projectionHash || null,
371
+ packageProductHash: compiled.packageProduct && compiled.packageProduct.projectionHash || null,
372
+ nativeEligibility: compiled.nativeEligibility,
373
+ commands,
374
+ application,
375
+ capabilities,
376
+ providerRequirements,
377
+ packages
378
+ },
379
+ loader: loaderObservation(javascriptApplication)
380
+ });
381
+ }
382
+
383
+ function eventRequirements(compiled) {
384
+ const requirements = new Set();
385
+ const catalog = compiled && (compiled.eventCatalog || compiled.metadata && compiled.metadata.events && compiled.metadata.events.catalog);
386
+ for (const event of catalog && Array.isArray(catalog.events) ? catalog.events : []) {
387
+ for (const requirement of event.hostRequirements || []) requirements.add(String(requirement));
388
+ }
389
+ const outbound = compiled && compiled.eventOutboundRequirements;
390
+ for (const requirement of outbound && outbound.hostRequirements || []) requirements.add(String(requirement));
391
+ for (const capability of compiled && compiled.metadata && compiled.metadata.capabilities || []) {
392
+ if (capability === 'event.emit') requirements.add('event.emit');
393
+ }
394
+ const order = new Map([['event.ingress', 0], ['event.emit', 1]]);
395
+ return Object.freeze([...requirements].sort((left, right) => (order.get(left) ?? 99) - (order.get(right) ?? 99) || left.localeCompare(right)));
396
+ }
397
+
398
+ function descriptorEventCapabilities(descriptor) {
399
+ const supported = new Set();
400
+ const capabilities = descriptor && descriptor.capabilities;
401
+ if (Array.isArray(capabilities)) {
402
+ for (const capability of capabilities) {
403
+ if (capability === 'event.ingress' || capability === 'event.emit') supported.add(capability);
404
+ }
405
+ } else if (capabilities && typeof capabilities === 'object') {
406
+ if (capabilities.events && capabilities.events.ingress === true) supported.add('event.ingress');
407
+ if (capabilities.events && capabilities.events.emit === true) supported.add('event.emit');
408
+ if (capabilities.core && capabilities.core.eventIngress === true) supported.add('event.ingress');
409
+ if (capabilities.effects && capabilities.effects.eventEmit === true) supported.add('event.emit');
410
+ }
411
+ return Object.freeze([...supported].sort());
412
+ }
413
+
414
+ function eventRequirementDecision(provider, requirement, supported, inspectionOnly) {
415
+ const operation = requirement === 'event.ingress' ? 'ingress' : 'emit';
416
+ if (inspectionOnly) {
417
+ return Object.freeze({
418
+ id: requirement,
419
+ status: 'inspection-only',
420
+ reasonId: 'compile-only-event-inspection',
421
+ owner: 'compiler'
422
+ });
423
+ }
424
+ if (supported) {
425
+ return Object.freeze({
426
+ id: requirement,
427
+ status: 'eligible',
428
+ reasonId: `${provider}-event-${operation}-supported`,
429
+ owner: `provider-${provider}`
430
+ });
431
+ }
432
+ return Object.freeze({
433
+ id: requirement,
434
+ status: 'blocked',
435
+ reasonId: `${provider}-event-${operation}-unavailable`,
436
+ owner: `provider-${provider}`
437
+ });
438
+ }
439
+
440
+ function eventDiagnosticCode(provider, blocked) {
441
+ if (!blocked) return null;
442
+ if (provider === 'fastly' && blocked.id === 'event.ingress') return 'PULSE_FASTLY_EVENT_INGRESS_UNSUPPORTED';
443
+ if (provider === 'fastly' && blocked.id === 'event.emit') return 'PULSE_FASTLY_EVENT_EMIT_UNSUPPORTED';
444
+ return 'PULSE_EVENT_TARGET_UNSUPPORTED';
445
+ }
446
+
447
+ function eventCommand(id, status, reasonId, owner) {
448
+ return Object.freeze({ id, status, reasonId, owner });
449
+ }
450
+
451
+ function buildEventTargetSupportEvidence(compiled, options = {}) {
452
+ const requirements = eventRequirements(compiled);
453
+ const reachable = Boolean(compiled && (compiled.eventTopology || compiled.metadata && compiled.metadata.events));
454
+ if (!reachable) return null;
455
+ const provider = String(options.provider || 'none');
456
+ const target = String(options.target || 'native');
457
+ const descriptor = options.descriptor || {};
458
+ const targetId = String(descriptor.targetId || descriptor.id || `${provider}-${target}`);
459
+ const inspectionOnly = provider === 'none';
460
+ const supportedCapabilities = descriptorEventCapabilities(descriptor);
461
+ const supported = new Set(supportedCapabilities);
462
+ const decisions = Object.freeze(requirements.map((requirement) => (
463
+ eventRequirementDecision(provider, requirement, supported.has(requirement), inspectionOnly)
464
+ )));
465
+ const blocked = decisions.find((entry) => entry.status === 'blocked') || null;
466
+ const status = inspectionOnly ? 'inspection-only' : (blocked ? 'blocked' : 'eligible');
467
+ const eventTestTargets = new Set(options.eventTestTargets || []);
468
+ const commands = Object.freeze([
469
+ eventCommand('build', status === 'eligible' && descriptor.commands && descriptor.commands.build === true ? 'eligible' : 'blocked', status === 'eligible' ? 'event-build-supported' : (blocked && blocked.reasonId || 'compile-only-event-inspection'), status === 'eligible' ? `provider-${provider}` : (blocked && blocked.owner || 'compiler')),
470
+ eventCommand('compile', 'eligible', 'provider-neutral-event-inspection', 'compiler'),
471
+ eventCommand('dev', descriptor.commands && descriptor.commands.dev === true ? 'http-only' : 'blocked', 'event-dev-ingress-unavailable', `provider-${provider}`),
472
+ eventCommand('doctor', 'eligible', 'event-eligibility-inspection', 'cli'),
473
+ eventCommand('inspect', 'eligible', 'event-catalog-inspection', 'cli'),
474
+ eventCommand('test', status === 'eligible' && descriptor.commands && descriptor.commands.test === true && eventTestTargets.has(target) ? 'eligible' : 'blocked', status === 'eligible' && eventTestTargets.has(target) ? 'event-test-harness-supported' : (blocked && blocked.reasonId || 'event-test-harness-unavailable'), status === 'eligible' && eventTestTargets.has(target) ? `provider-${provider}` : (blocked && blocked.owner || `provider-${provider}`))
475
+ ].sort((left, right) => left.id.localeCompare(right.id)));
476
+ const document = Object.freeze({
477
+ version: EVENT_TARGET_SUPPORT_VERSION,
478
+ provider,
479
+ target,
480
+ targetId,
481
+ status,
482
+ eligible: status === 'eligible',
483
+ inspectionOnly,
484
+ automaticFallback: false,
485
+ requirements: decisions,
486
+ supportedCapabilities,
487
+ reasonIds: Object.freeze([...new Set(decisions
488
+ .filter((entry) => entry.status !== 'eligible')
489
+ .map((entry) => entry.reasonId))].sort()),
490
+ diagnosticCode: eventDiagnosticCode(provider, blocked),
491
+ commands
492
+ });
493
+ return Object.freeze({ ...document, evidenceHash: sha256Hex(stableStringify(document)) });
494
+ }
495
+
496
+ module.exports = Object.freeze({
497
+ EVENT_TARGET_SUPPORT_VERSION,
498
+ buildJavascriptTargetSupportEvidence,
499
+ buildEventTargetSupportEvidence
500
+ });