@pulse-compute/wasm-compiler 1.0.0-beta.6 → 1.0.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,9 +8,9 @@
8
8
  > **Stability:** Internal compiler interface; no application-author compatibility guarantee.<br>
9
9
  > **npm:** [`@pulse-compute/wasm-compiler`](https://www.npmjs.com/package/@pulse-compute/wasm-compiler)<br>
10
10
  > **Canonical replacement:** `@pulse-compute/cli`<br>
11
- > **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.6/packages/implementation-packages/)
11
+ > **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.8/packages/implementation-packages/)
12
12
  >
13
- > This release-status block is generated from the synchronized `Pulse 1.0.0-beta.6` package policy.
13
+ > This release-status block is generated from the synchronized `Pulse 1.0.0-beta.8` package policy.
14
14
  <!-- pulse-package-status:end -->
15
15
 
16
16
  Whole-project compiler implementation used by the `pulse` workflow. Application projects should use `@pulse-compute/cli`, not import this package directly.
@@ -63,4 +63,4 @@ The package exports its root implementation plus focused compiler, CLI, extracto
63
63
 
64
64
  Compiler changes should be covered by focused static/lowering tests, canonical project inspection, provider-plan parity, packed-package discovery where relevant, and the release documentation gates.
65
65
 
66
- See the installed [implementation package guide](https://pulsecompute.io/v1.0.0-beta.6/packages/implementation-packages/) and [compilation concepts](https://pulsecompute.io/v1.0.0-beta.6/concepts/compilation-and-lowering/).
66
+ See the installed [implementation package guide](https://pulsecompute.io/v1.0.0-beta.8/packages/implementation-packages/) and [compilation concepts](https://pulsecompute.io/v1.0.0-beta.8/concepts/compilation-and-lowering/).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pulse-compute/wasm-compiler",
3
- "version": "1.0.0-beta.6",
3
+ "version": "1.0.0-beta.8",
4
4
  "license": "Apache-2.0",
5
5
  "description": "PulseWasm compiler package inside the Pulse monorepo.",
6
6
  "type": "commonjs",
@@ -9,13 +9,13 @@
9
9
  "pulsewasm": "./bin/pulsewasm-extract.js"
10
10
  },
11
11
  "dependencies": {
12
- "@pulse-compute/wasm-build-support": "1.0.0-beta.6",
13
- "@pulse-compute/wasm-contracts": "1.0.0-beta.6",
14
- "@pulse-compute/wasm-guest-link": "1.0.0-beta.6",
15
- "@pulse-compute/wasm-host-runtime": "1.0.0-beta.6",
16
- "@pulse-compute/wasm-library-kit": "1.0.0-beta.6",
17
- "@pulse-compute/wasm-runtime-core-as": "1.0.0-beta.6",
18
- "@pulse-compute/wasm-schema-json": "1.0.0-beta.6",
12
+ "@pulse-compute/wasm-build-support": "1.0.0-beta.8",
13
+ "@pulse-compute/wasm-contracts": "1.0.0-beta.8",
14
+ "@pulse-compute/wasm-guest-link": "1.0.0-beta.8",
15
+ "@pulse-compute/wasm-host-runtime": "1.0.0-beta.8",
16
+ "@pulse-compute/wasm-library-kit": "1.0.0-beta.8",
17
+ "@pulse-compute/wasm-runtime-core-as": "1.0.0-beta.8",
18
+ "@pulse-compute/wasm-schema-json": "1.0.0-beta.8",
19
19
  "assemblyscript": "0.28.18",
20
20
  "json-as": "1.5.0",
21
21
  "typescript": "5.9.3"
@@ -56,7 +56,7 @@
56
56
  "url": "git+https://github.com/pulse-compute/pulse.git",
57
57
  "directory": "wasm/packages/compiler"
58
58
  },
59
- "homepage": "https://pulsecompute.io/v1.0.0-beta.6/packages/implementation-packages/",
59
+ "homepage": "https://pulsecompute.io/v1.0.0-beta.8/packages/implementation-packages/",
60
60
  "bugs": {
61
61
  "url": "https://github.com/pulse-compute/pulse/issues"
62
62
  },
@@ -24,6 +24,7 @@ const eventContract = require('@pulse-compute/wasm-contracts/events');
24
24
  const { resolveAsc } = require('@pulse-compute/wasm-build-support/assemblyscript-compile');
25
25
  const { memoryAbi } = require('@pulse-compute/wasm-guest-link');
26
26
  const {
27
+ prepareReportCapture,
27
28
  appendAssemblyScriptOptimizationArgs,
28
29
  resolveNativeOptimization
29
30
  } = require('@pulse-compute/wasm-build-support/native-optimization');
@@ -220,7 +221,10 @@ function realizeCanonicalNativePlan(plan, options = {}, providerRequirements) {
220
221
  '--path', jsonAs.dependencyRoot
221
222
  );
222
223
  }
223
- const startedAt = Date.now();
224
+ require('@pulse-compute/wasm-build-support/native-optimization').appendJsonCacheArenaArgs(args, options.elideJsonCacheArena, schemaCodecsActive);
225
+ const readReportCapture = options.reportCapture === false ? () => null : prepareReportCapture(args, stagingDir, generated.manifest,
226
+ require(path.join(asc.packageRoot, 'package.json')).version, 'canonical-native.as/');
227
+ const startedAt = process.hrtime.bigint();
224
228
  const result = spawnSync(asc.executable, args, {
225
229
  cwd: stagingDir,
226
230
  encoding: 'utf8',
@@ -230,7 +234,7 @@ function realizeCanonicalNativePlan(plan, options = {}, providerRequirements) {
230
234
  ? { ...process.env, JSON_STRICT: 'true', JSON_USE_FAST_PATH: '0', JSON_MODE: 'NAIVE' }
231
235
  : process.env
232
236
  });
233
- const durationMs = Date.now() - startedAt;
237
+ const durationMs = Math.round(Number(process.hrtime.bigint() - startedAt) / 1e6);
234
238
  if (result.error || result.status !== 0 || !fs.existsSync(wasmFile)) {
235
239
  throw new CanonicalNativeCompileError('AssemblyScript failed to compile the canonical native plan.', {
236
240
  status: result.status,
@@ -242,15 +246,23 @@ function realizeCanonicalNativePlan(plan, options = {}, providerRequirements) {
242
246
  durationMs
243
247
  });
244
248
  }
249
+ const wasm = fs.readFileSync(wasmFile);
245
250
  return Object.freeze({
246
251
  plan,
247
252
  validation,
248
253
  realizationArtifacts,
249
254
  guestUnits,
250
255
  generated,
251
- wasm: fs.readFileSync(wasmFile),
256
+ reportAttribution: readReportCapture(wasm),
257
+ buildObservation: require('@pulse-compute/wasm-build-support/native-optimization').observeAssemblyScript(args, {
258
+ version: require(path.join(asc.packageRoot, 'package.json')).version,
259
+ assemblyScriptRoot: asc.packageRoot, timeoutMs: Number(options.timeoutMs || 120000),
260
+ compilerVersion: runtimeContract.CANONICAL_NATIVE_COMPILER_VERSION, durationMs, jsonAsVersion: jsonAs && jsonAs.version
261
+ }),
262
+ wasm,
252
263
  wat: emitWat ? fs.readFileSync(watFile, 'utf8') : '',
253
264
  textEmitted: emitWat,
265
+ elideJsonCacheArena: options.elideJsonCacheArena === true,
254
266
  assemblyScriptVersion: require(path.join(asc.packageRoot, 'package.json')).version,
255
267
  jsonAsVersion: jsonAs && jsonAs.version,
256
268
  optimization,
@@ -281,8 +293,9 @@ function verifyCanonicalNativeRealization(realization) {
281
293
  compilerVersion: runtimeContract.CANONICAL_NATIVE_COMPILER_VERSION,
282
294
  assemblyScript: Object.freeze({ package: 'assemblyscript', version: realization.assemblyScriptVersion }),
283
295
  optimization: resolveNativeOptimization(realization.optimization),
296
+ elideJsonCacheArena: realization.elideJsonCacheArena,
284
297
  jsonAs: realization.jsonAsVersion
285
- ? Object.freeze({ package: 'json-as', version: realization.jsonAsVersion, transform: true, strict: true, mode: 'NAIVE', fastPath: false })
298
+ ? Object.freeze({ package: 'json-as', version: realization.jsonAsVersion, transform: true, strict: true, mode: 'NAIVE', fastPath: false, elideDisabledCacheArena: realization.elideJsonCacheArena })
286
299
  : null,
287
300
  wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
288
301
  wat: Object.freeze(realization.textEmitted
@@ -305,6 +318,7 @@ function verifyCanonicalNativeRealization(realization) {
305
318
  compilerVersion: runtimeContract.CANONICAL_NATIVE_COMPILER_VERSION,
306
319
  plan: realization.plan,
307
320
  validation: realization.validation,
321
+ reportAttribution: realization.reportAttribution && realization.reportAttribution.artifactSha256 === sha256(realization.wasm) ? realization.reportAttribution : null,
308
322
  generated: realization.generated,
309
323
  source: realization.generated.source,
310
324
  sourceHash: realization.generated.sourceHash,
@@ -315,6 +329,7 @@ function verifyCanonicalNativeRealization(realization) {
315
329
  wat: realization.wat,
316
330
  inspection,
317
331
  manifest,
332
+ buildObservation: realization.buildObservation,
318
333
  durationMs: realization.durationMs,
319
334
  output: realization.output
320
335
  });
@@ -1905,6 +1905,7 @@ module.exports = Object.freeze({
1905
1905
  CANONICAL_NATIVE_PLAN_OWNERSHIP_VERSION: contract.CANONICAL_NATIVE_PLAN_OWNERSHIP_VERSION,
1906
1906
  CanonicalNativePlanError,
1907
1907
  buildCanonicalNativePlan,
1908
+ collectCanonicalReportReferences: require('./report-reference-provenance').collectCanonicalReportReferences,
1908
1909
  validateCanonicalNativePlan: assertCanonicalNativePlan,
1909
1910
  writeCanonicalNativePlan,
1910
1911
  stableStringify
@@ -1043,6 +1043,7 @@ function compileCanonicalProjectLegacy(entryFile, options = {}) {
1043
1043
  try {
1044
1044
  managedHandlers = compileManagedHandlerDescriptors({
1045
1045
  graphBuild,
1046
+ target: options.target,
1046
1047
  descriptors: packageOperationRecognition.managedHandlers,
1047
1048
  packageOperationRecognition
1048
1049
  });
@@ -0,0 +1,121 @@
1
+ 'use strict';
2
+
3
+ const { packageOperationRecognitionForCompiled } = require('./spine/package-operation-seam');
4
+
5
+ // Build-time projection of compiler facts. This record never enters the Native
6
+ // plan, executable source, recipe or provider configuration. Unknown expressions
7
+ // are not evaluated or serialized, and reference resolution is not host binding.
8
+ function collectCanonicalReportReferences(compiled, plan) {
9
+ const raw = compiled.metadata.applicationEntries || plan.routing?.entries || [];
10
+ const entries = new Set(raw.map(row => row.stableId));
11
+ if (!raw.length) entries.add('default');
12
+ const helpers = new Map(), rows = new Map();
13
+ const schemas = new Set((compiled.schema?.registry || plan.schemas?.registry)?.schemas?.map(row => row.id) || []);
14
+ const unique = values => [...new Set(values)].sort();
15
+ function calls(value, stageConsumers) {
16
+ if (!value || typeof value !== 'object') return;
17
+ if (value.kind === 'helper-call' || value.kind === 'pure-helper-call') {
18
+ if (!helpers.has(value.helperId)) helpers.set(value.helperId, new Set());
19
+ for (const entry of stageConsumers || [value.callerEntryId]) helpers.get(value.helperId).add(entry);
20
+ }
21
+ for (const child of Object.values(value)) if (child && typeof child === 'object') {
22
+ if (Array.isArray(child)) child.forEach(row => calls(row, stageConsumers)); else calls(child, stageConsumers);
23
+ }
24
+ }
25
+ for (const body of [plan.entry.body, ...(plan.handlers || []).map(row => row.body)]) calls(body);
26
+ for (const stage of plan.stages || []) calls(stage.body, stage.registrations.map(row => row.entryId));
27
+ // Package provenance comes from the trusted recognition bundle, never a file
28
+ // name, schema name or usage-string guess. Keep it outside executable metadata.
29
+ const packageSources = new Map();
30
+ const referenceKey = row => JSON.stringify([row.id, row.usage, row.capability,
31
+ row.file || compiled.metadata.file || '', row.position?.offset]);
32
+ for (const source of packageOperationRecognitionForCompiled(compiled)?.publicExtensions?.plans || []) {
33
+ for (const reference of source.schemaReferences || []) {
34
+ const key = referenceKey(reference);
35
+ if (!packageSources.has(key)) packageSources.set(key, new Set());
36
+ packageSources.get(key).add(source.package);
37
+ }
38
+ }
39
+ function owners(effect) {
40
+ if (effect.helperId) return [...(helpers.get(effect.helperId) || [])];
41
+ const entry = effect.applicationEntryStableId || effect.routerEntryStableId;
42
+ return entry ? [entry] : raw.length ? [] : ['default'];
43
+ }
44
+ function add(kind, canonicalId, bindingKind, consumers, observationId, detail = {}) {
45
+ const state = canonicalId === null ? detail.dynamic ? 'dynamic' : 'unknown' : detail.unresolved ? 'unknown' : 'resolved';
46
+ const key = JSON.stringify([kind, bindingKind, state, state === 'resolved' ? canonicalId : observationId]);
47
+ let row = rows.get(key);
48
+ if (!row) {
49
+ row = { kind, canonicalId, bindingKind, state, observationId: state === 'resolved' ? null : observationId,
50
+ entryIds: [], entriesComplete: true, ...(detail.resource ? { resource: detail.resource } : {}) };
51
+ rows.set(key, row);
52
+ }
53
+ row.entryIds.push(...consumers.filter(id => entries.has(id)));
54
+ row.entriesComplete &&= consumers.length > 0 && consumers.every(id => entries.has(id));
55
+ if (detail.externalPackages?.length) row.externalPackages = unique([...(row.externalPackages || []), ...detail.externalPackages]);
56
+ }
57
+ const literal = value => value?.kind === 'literal' && typeof value.value === 'string' ? value.value : null;
58
+ const input = (effect, name) => effect.inputs?.find(row => row.name === name)?.value;
59
+ const field = (object, name) => object?.kind === 'object'
60
+ ? object.entries?.find(row => row.key?.kind === 'literal' && row.key.value === name)?.value : null;
61
+ for (const effect of plan.effects || []) {
62
+ const consumers = owners(effect), kind = effect.providerKind;
63
+ if (['config', 'secret', 'kv', 's3'].includes(kind)) {
64
+ const expression = kind === 's3' ? field(input(effect, 'resource'), 'binding')
65
+ : input(effect, kind === 'kv' ? 'store' : 'name');
66
+ const resolved = literal(expression);
67
+ const name = resolved ?? (effect.resource?.kind === 'literal' && typeof effect.resource.value === 'string' ? effect.resource.value : null);
68
+ add('binding', name, kind, consumers, effect.id, {
69
+ dynamic: effect.resource?.kind === 'dynamic' || Boolean(expression && expression.kind !== 'literal')
70
+ });
71
+ }
72
+ if (effect.kind === 'event.emit') {
73
+ const schema = literal(field(input(effect, 'emission'), 'schema'));
74
+ if (schema !== null) add('schema', schema, null, consumers, effect.id, { unresolved: !schemas.has(schema) });
75
+ }
76
+ if (effect.kind !== 'assets.lookup') continue;
77
+ const payload = input(effect, 'payload'), embeddedId = literal(field(payload, 'embeddedId'));
78
+ const key = literal(field(payload, 'key'));
79
+ const found = field(payload, 'embeddedFound')?.value === true;
80
+ const bytes = field(payload, 'embeddedLength')?.value;
81
+ const encoded = literal(field(payload, 'embeddedData'));
82
+ const known = embeddedId !== null && key !== null && found && Number.isSafeInteger(bytes) && bytes >= 0;
83
+ add('resource', embeddedId !== null && key !== null ? embeddedId + ':' + key : null, null, consumers, effect.id, {
84
+ unresolved: !known,
85
+ dynamic: Boolean(field(payload, 'key') && field(payload, 'key').kind !== 'literal'),
86
+ ...(known ? { resource: { name: key, mediaType: literal(field(payload, 'embeddedType')), inputBytes: bytes,
87
+ encodedBytes: encoded === null ? null : Buffer.byteLength(encoded, 'utf8') } } : {})
88
+ });
89
+ }
90
+ // These offsets are compiler-generated entry/helper ranges, not a reader's source
91
+ // heuristic. Authored package references from another file cannot borrow an
92
+ // unrelated generated offset. Unassociated references remain observations.
93
+ for (const [index, reference] of (compiled.metadata.schemaReferences || []).entries()) {
94
+ const sameFile = !reference.file || reference.file === compiled.metadata.file;
95
+ const offset = reference.position?.offset;
96
+ const contains = row => row.generatedRange && offset >= row.generatedRange.start && offset < row.generatedRange.end;
97
+ const located = sameFile && Number.isSafeInteger(offset);
98
+ const matches = located ? raw.filter(contains) : [];
99
+ const helperMatches = located ? (plan.routing?.helpers || []).filter(contains) : [];
100
+ const consumers = !raw.length && sameFile ? ['default']
101
+ : matches.length === 1 && !helperMatches.length ? [matches[0].stableId]
102
+ : !matches.length && helperMatches.length === 1 ? [...(helpers.get(helperMatches[0].id) || [])] : [];
103
+ const name = typeof reference.id === 'string' && reference.id ? reference.id : null;
104
+ add('schema', name, null, consumers, 'schema:' + index, { unresolved: !schemas.has(name),
105
+ externalPackages: [...(packageSources.get(referenceKey(reference)) || [])] });
106
+ }
107
+ for (const event of compiled.eventCatalog?.events || []) if (event.schemaId) {
108
+ add('schema', event.schemaId, null, [event.stableId], event.stableId, { unresolved: !schemas.has(event.schemaId) });
109
+ }
110
+ // Only compact allowlisted data crosses into Report. No provider metadata,
111
+ // effect payloads, binding values, resource bodies or expression text survives.
112
+ const routeBehaviors = require('./report-route-behavior').routeBehaviorForMetadata(compiled.metadata.router);
113
+ return Object.freeze({ version: 'pulse.compiler-report-references.v1',
114
+ ...(routeBehaviors ? { routeBehaviors } : {}),
115
+ responsePayloads: require('./report-response-payloads').collectResponsePayloads(plan, entries, helpers),
116
+ references: Object.freeze([...rows.values()].map(row => Object.freeze({ ...row, entryIds: Object.freeze(unique(row.entryIds)),
117
+ ...(row.externalPackages ? { externalPackages: Object.freeze(row.externalPackages) } : {}),
118
+ ...(row.resource ? { resource: Object.freeze(row.resource) } : {}) }))) });
119
+ }
120
+
121
+ module.exports = { collectCanonicalReportReferences };
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ // Observational projection of canonical text-response sites, after emission.
4
+ // Never evaluate application expressions or expose literal contents/hashes.
5
+ function collectResponsePayloads(plan, entryIds, helperConsumers) {
6
+ const sites = [];
7
+ const unique = values => [...new Set(values)].sort();
8
+ const literal = value => value?.kind === 'literal' && typeof value.value === 'string' ? value.value : null;
9
+ const objectFields = value => value?.kind === 'object' && value.entries.every(row => row.kind === 'property'
10
+ && row.key?.kind === 'literal' && typeof row.key.value === 'string') ? value.entries : null;
11
+ const field = (rows, key) => rows?.filter(row => row.key.value === key).at(-1)?.value;
12
+ function mediaType(options) {
13
+ if (!options || options.kind === 'undefined') return 'text/plain';
14
+ const fields = objectFields(options);
15
+ if (!fields) return null;
16
+ const headers = field(fields, 'headers');
17
+ if (!headers) return 'text/plain';
18
+ const values = objectFields(headers);
19
+ if (!values) return null;
20
+ const types = values.filter(row => row.key.value.toLowerCase() === 'content-type');
21
+ if (!types.length) return 'text/plain';
22
+ // Case variants can be merged by the runtime header adapter; do not guess.
23
+ if (types.length !== 1) return null;
24
+ const type = literal(types[0].value)?.split(';')[0].trim().toLowerCase();
25
+ return ['text/plain', 'text/css', 'text/html', 'text/javascript', 'application/javascript'].includes(type) ? type : null;
26
+ }
27
+ function scan(body, owner, consumers) {
28
+ const constants = new Map();
29
+ function visit(value, fn, path = 'body') {
30
+ if (!value || typeof value !== 'object') return;
31
+ fn(value, path);
32
+ for (const [key, child] of Object.entries(value)) if (child && typeof child === 'object') {
33
+ if (Array.isArray(child)) child.forEach((row, i) => visit(row, fn, path + '.' + key + '[' + i + ']'));
34
+ else visit(child, fn, path + '.' + key);
35
+ }
36
+ }
37
+ visit(body, node => {
38
+ if (node.kind === 'local' && node.declaration === 'const' && literal(node.value) !== null)
39
+ constants.set(node.localId, node.value.value);
40
+ });
41
+ visit(body, (node, path) => {
42
+ if (node.kind !== 'intrinsic' || node.name !== 'response.text') return;
43
+ const input = node.arguments[0];
44
+ const value = literal(input) ?? (input?.kind === 'local' ? constants.get(input.id) : null);
45
+ const known = typeof value === 'string';
46
+ const state = known ? 'resolved' : !input || ['literal', 'undefined'].includes(input.kind) ? 'unknown' : 'dynamic';
47
+ const valid = unique(consumers.filter(id => entryIds.has(id)));
48
+ sites.push(Object.freeze({ canonicalId: JSON.stringify([owner, path]),
49
+ state, inputBytes: known ? Buffer.byteLength(value, 'utf8') : null,
50
+ mediaType: mediaType(node.arguments[1]), entryIds: Object.freeze(valid),
51
+ entriesComplete: consumers.length > 0 && consumers.every(id => entryIds.has(id)) }));
52
+ });
53
+ }
54
+ scan(plan.entry.body, 'entry', plan.entry.kind === 'router' ? [] : ['default']);
55
+ for (const row of plan.handlers || []) scan(row.body, 'handler:' + row.id, [row.id]);
56
+ for (const row of plan.stages || []) scan(row.body, 'stage:' + row.id, row.registrations.map(item => item.entryId));
57
+ for (const row of plan.helpers || []) scan(row.body, 'helper:' + row.id, [...(helperConsumers.get(row.id) || [])]);
58
+ sites.sort((a, b) => a.canonicalId < b.canonicalId ? -1 : a.canonicalId > b.canonicalId ? 1 : 0);
59
+ return Object.freeze({ version: 'pulse.report-text-responses.v1', sites: Object.freeze(sites) });
60
+ }
61
+
62
+ module.exports = { collectResponsePayloads };
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ // Observational only: keep IR facts out of serialized compiler metadata, source
4
+ // and plan hashes. A deserialized/foreign metadata object supplies no evidence.
5
+ const records = new WeakMap();
6
+ function retainRouteBehavior(metadata, handlers) {
7
+ const routes = new Set(metadata.entries.filter(row => row.kind === 'route').map(row => row.stableId));
8
+ records.set(metadata, Object.freeze(handlers.filter(row => routes.has(row.entryStableId)).map(row => Object.freeze({
9
+ entryId: row.entryStableId,
10
+ kind: row.operationIr.summary.operationKinds['router-transfer'] ? 'continuing' : 'terminal',
11
+ basis: 'canonical-handler-ir'
12
+ }))));
13
+ }
14
+ function routeBehaviorForMetadata(metadata) { return records.get(metadata); }
15
+ module.exports = { retainRouteBehavior, routeBehaviorForMetadata };
@@ -118,6 +118,7 @@ function executeCanonicalRouterSpine(sourceText, options = {}) {
118
118
  if (output && typeof output === 'object') {
119
119
  traces.set(output, result.trace);
120
120
  handlerIrBundles.set(output, bundle.handlers);
121
+ require('../report-route-behavior').retainRouteBehavior(output.metadata, bundle.handlers);
121
122
  surfaceFacts.set(output, Object.freeze({
122
123
  recognition: bundle.recognition,
123
124
  classification: bundle.classification,
@@ -815,7 +815,7 @@ function effectPlanForHandler(options) {
815
815
  });
816
816
  }
817
817
 
818
- function compileHandler(resolved, graphBuild, allResolved, diagnostics, packageRecognition) {
818
+ function compileHandler(resolved, graphBuild, allResolved, diagnostics, packageRecognition, options = {}) {
819
819
  const descriptor = resolved.descriptor;
820
820
  const sourceFile = resolved.sourceModule.sourceFile;
821
821
  const original = resolved.functionNode;
@@ -868,10 +868,18 @@ function compileHandler(resolved, graphBuild, allResolved, diagnostics, packageR
868
868
  frontend: 'canonical-source',
869
869
  role: MANAGED_HANDLER_ROLE,
870
870
  strict: true,
871
- requireEffectAwait: false,
871
+ requireEffectAwait: options.target === 'javascript',
872
+ target: options.target,
872
873
  packageEffectForCall
873
874
  });
874
875
  diagnostics.push(...normalizer.diagnostics);
876
+ // JavaScript executes the original source graph. Native expression admission
877
+ // is independent, and cannot prevent inspection of that explicit target.
878
+ const nativeDiagnostics = options.target === 'javascript' ? [] : diagnostics;
879
+ if (options.target === 'javascript') nativeDiagnostics.push(...normalizeManagedHandler(original, {
880
+ sourceFile, ctxName, frontend: 'canonical-source', role: MANAGED_HANDLER_ROLE,
881
+ strict: true, requireEffectAwait: false, packageEffectForCall
882
+ }).diagnostics);
875
883
  const handler = normalizer.functionNode;
876
884
 
877
885
  const schemaReferences = Object.freeze([
@@ -896,6 +904,7 @@ function compileHandler(resolved, graphBuild, allResolved, diagnostics, packageR
896
904
  })
897
905
  };
898
906
  const rawOperationIr = buildPlainHandlerIr(frontend, {
907
+ target: options.target,
899
908
  packageEffects: packageInputs.effects,
900
909
  packageIntrinsics: packageInputs.intrinsics,
901
910
  packageResultAdapters: packageInputs.resultAdapters,
@@ -916,7 +925,7 @@ function compileHandler(resolved, graphBuild, allResolved, diagnostics, packageR
916
925
  const compileExpression = createExpressionCompiler({
917
926
  sourceFile,
918
927
  descriptor,
919
- diagnostics,
928
+ diagnostics: nativeDiagnostics,
920
929
  ctxName,
921
930
  inputName,
922
931
  handlerTargets,
@@ -938,7 +947,7 @@ function compileHandler(resolved, graphBuild, allResolved, diagnostics, packageR
938
947
  ]);
939
948
 
940
949
  function fail(node, code, message, detail = {}) {
941
- diagnostics.push(diagnostic({ descriptor, sourceFile, node, code, message, detail }));
950
+ nativeDiagnostics.push(diagnostic({ descriptor, sourceFile, node, code, message, detail }));
942
951
  }
943
952
 
944
953
  function registerEffectRuntimeInputs(site, candidate, scope) {
@@ -1377,6 +1386,9 @@ function compileHandler(resolved, graphBuild, allResolved, diagnostics, packageR
1377
1386
  packageEffects: packageInputs.effects,
1378
1387
  packageLowering: packageInputs.lowering
1379
1388
  });
1389
+ if (options.target === 'javascript') diagnostics.push(...nativeDiagnostics.filter(entry => [
1390
+ 'PULSE_MANAGED_HANDLER_RECURSION_UNSUPPORTED', 'PULSE_MANAGED_HANDLER_DIRECT_CALL_UNSUPPORTED'
1391
+ ].includes(entry.code)));
1380
1392
  const semantic = {
1381
1393
  version: managedHandlerIrVersion,
1382
1394
  kind: 'managed-handler',
@@ -1408,7 +1420,8 @@ function compileHandler(resolved, graphBuild, allResolved, diagnostics, packageR
1408
1420
  inspected: true,
1409
1421
  packageTargetPromotion: false,
1410
1422
  javascript: Object.freeze({ eligible: true, blockers: Object.freeze([]) }),
1411
- native: Object.freeze({ eligible: true, blockers: Object.freeze([]) })
1423
+ native: Object.freeze({ eligible: options.target !== 'javascript' || nativeDiagnostics.length === 0,
1424
+ blockers: Object.freeze(options.target === 'javascript' ? sortedUnique(nativeDiagnostics.map(entry => entry.code)) : []) })
1412
1425
  })
1413
1426
  };
1414
1427
  const record = deepFreeze({ ...semantic, handlerHash: sha256Hex(stableStringify(semantic)) });
@@ -1465,7 +1478,7 @@ function compileManagedHandlerDescriptors(inputs = {}) {
1465
1478
  const resolved = resolveDescriptors(inputs.graphBuild, descriptors, diagnostics);
1466
1479
  const handlers = [];
1467
1480
  for (const entry of resolved) {
1468
- const handler = compileHandler(entry, inputs.graphBuild, resolved, diagnostics, packageRecognition);
1481
+ const handler = compileHandler(entry, inputs.graphBuild, resolved, diagnostics, packageRecognition, { target: inputs.target });
1469
1482
  if (handler) handlers.push(handler);
1470
1483
  }
1471
1484
  if (diagnostics.length > 0) {