@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 +3 -3
- package/package.json +9 -9
- package/src/canonical-native-compiler.js +19 -4
- package/src/canonical-native-plan.js +1 -0
- package/src/canonical-project-compiler.js +1 -0
- package/src/report-reference-provenance.js +121 -0
- package/src/report-response-payloads.js +62 -0
- package/src/report-route-behavior.js +15 -0
- package/src/spine/canonical-router.js +1 -0
- package/src/spine/handler-ir-managed.js +19 -6
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
13
|
-
"@pulse-compute/wasm-contracts": "1.0.0-beta.
|
|
14
|
-
"@pulse-compute/wasm-guest-link": "1.0.0-beta.
|
|
15
|
-
"@pulse-compute/wasm-host-runtime": "1.0.0-beta.
|
|
16
|
-
"@pulse-compute/wasm-library-kit": "1.0.0-beta.
|
|
17
|
-
"@pulse-compute/wasm-runtime-core-as": "1.0.0-beta.
|
|
18
|
-
"@pulse-compute/wasm-schema-json": "1.0.0-beta.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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) {
|