@pulse-compute/wasm-compiler 1.0.0-beta.7 → 1.0.0-beta.9
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 +20 -4
- package/src/canonical-native-plan.js +1 -0
- package/src/canonical-project-compiler.js +8 -1
- package/src/javascript-application-plan.js +1 -0
- package/src/project/npm-source-resolution.js +91 -0
- package/src/project/package-reachability.js +2 -1
- package/src/project/reachable-graph-builder.js +131 -19
- package/src/report-handler-topology.js +104 -0
- package/src/report-reference-provenance.js +123 -0
- package/src/report-response-payloads.js +62 -0
- package/src/report-route-behavior.js +15 -0
- package/src/report-router-control.js +42 -0
- package/src/report-source-inputs.js +55 -0
- package/src/spine/binding-ownership.js +13 -0
- package/src/spine/canonical-router.js +1 -0
- package/src/spine/handler-surface-authority.js +71 -0
- package/src/spine/package-operation-seam.js +21 -7
- package/src/spine/router-handler-frontend.js +6 -13
- package/src/spine/terminal-package-native.js +4 -2
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createHash } = require('node:crypto');
|
|
4
|
+
const { topologyForCanonicalRouterOutput, surfaceFactsForCanonicalRouterOutput, handlerIrsForCanonicalRouterOutput } = require('./spine/canonical-router');
|
|
5
|
+
const { sourceModelForRouterRecognition, capabilityForHandlerSurface, staticString } = require('./spine/handler-surface-authority');
|
|
6
|
+
const { projectRouterControl } = require('./report-router-control');
|
|
7
|
+
const { bindingOwnership } = require('./spine/binding-ownership');
|
|
8
|
+
const unavailable = () => ({ state: 'unavailable', value: null, reason: 'not-recorded' });
|
|
9
|
+
const available = value => ({ state: 'available', value, reason: null });
|
|
10
|
+
const freeze = value => { if (value && typeof value === 'object') { Object.values(value).forEach(freeze); Object.freeze(value); } return value; };
|
|
11
|
+
|
|
12
|
+
// Post-emission observation only. Read retained compiler objects, never reparse
|
|
13
|
+
// emitted source or read files. Nothing here enters executable metadata/hashes.
|
|
14
|
+
function collectHandlerTopology(compiled) {
|
|
15
|
+
const topology = topologyForCanonicalRouterOutput(compiled.router);
|
|
16
|
+
if (!topology) return undefined;
|
|
17
|
+
// A newer/other topology must not turn successful compilation into a report
|
|
18
|
+
// failure, nor acquire a fabricated role. Its analytics remain unavailable.
|
|
19
|
+
if (topology.entries.some(({ entry }) => !['route', 'use', 'mount', 'error', 'event', 'fallback'].includes(entry.kind))) return undefined;
|
|
20
|
+
const linked = topology.options.linkedProjectModules;
|
|
21
|
+
const files = new Map();
|
|
22
|
+
const sourceFile = file => linked?.sourceFileForPath(file)
|
|
23
|
+
|| (topology.sourceFile.fileName === file ? topology.sourceFile : undefined);
|
|
24
|
+
function span(file, start, end) {
|
|
25
|
+
const source = sourceFile(file);
|
|
26
|
+
if (!source || !Number.isSafeInteger(start) || !Number.isSafeInteger(end)
|
|
27
|
+
|| start < 0 || end <= start || end > source.text.length) return unavailable();
|
|
28
|
+
if (!files.has(file)) files.set(file, createHash('sha256').update(source.text).digest('hex'));
|
|
29
|
+
const point = offset => { const p = source.getLineAndCharacterOfPosition(offset);
|
|
30
|
+
return { offset, line: p.line + 1, column: p.character + 1 }; };
|
|
31
|
+
return available({ file, sha256: files.get(file), start: point(start), end: point(end) });
|
|
32
|
+
}
|
|
33
|
+
const location = loc => span(loc?.file, loc?.start?.offset, loc?.end?.offset);
|
|
34
|
+
const modules = new Map((compiled.reachableGraph?.modules || []).filter(m => m.path).map(m => [m.path, m]));
|
|
35
|
+
function packageFor(file) {
|
|
36
|
+
const module = modules.get(file);
|
|
37
|
+
if (!module) return unavailable();
|
|
38
|
+
return module.packageName && module.packageVersion
|
|
39
|
+
? available({ name: module.packageName, version: module.packageVersion, sourceIdentity: module.path })
|
|
40
|
+
: { state: 'not-applicable', value: null, reason: 'not-applicable' };
|
|
41
|
+
}
|
|
42
|
+
const handlers = topology.handlerTable.handlers.map(handler => ({
|
|
43
|
+
id: handler.id, kind: 'handler', name: handler.localName || handler.exportName || handler.inlineName || null,
|
|
44
|
+
source: location(handler.loc), package: packageFor(handler.file),
|
|
45
|
+
evidence: [{ basis: 'handler-table', producer: 'canonical-router-handler-table' }]
|
|
46
|
+
}));
|
|
47
|
+
for (const helper of linked?.sourceHelpers || []) {
|
|
48
|
+
const file = helper.source.file, node = helper.functionNode, source = sourceFile(file);
|
|
49
|
+
handlers.push({ id: helper.id, kind: 'helper', name: helper.source.name || null,
|
|
50
|
+
source: source && node ? span(file, node.getStart(source), node.end) : unavailable(), package: packageFor(file),
|
|
51
|
+
evidence: [{ basis: 'handler-table', producer: 'canonical-router-linked-helper' }] });
|
|
52
|
+
}
|
|
53
|
+
const http = new Map(topology.entries.filter(d => d.entry.kind !== 'event').map(d => [d.entry.index, d.entry.stableId]));
|
|
54
|
+
const controls = new Map((handlerIrsForCanonicalRouterOutput(compiled.router) || []).map(row => [row.entryStableId, row.operationIr]));
|
|
55
|
+
const mounts = [];
|
|
56
|
+
const registrations = topology.entries.map(({ entry, rawEntry }) => {
|
|
57
|
+
// The compiler's child/parent-return interval identifies this particular
|
|
58
|
+
// mount expansion, even when router definition paths are identical.
|
|
59
|
+
while (mounts.length && entry.index >= mounts.at(-1).end) mounts.pop();
|
|
60
|
+
const registration = {
|
|
61
|
+
id: entry.stableId, handlerId: entry.handlerId || null,
|
|
62
|
+
kind: entry.kind === 'use' ? 'middleware' : entry.kind,
|
|
63
|
+
authored: Boolean(rawEntry.loc) && entry.kind !== 'fallback',
|
|
64
|
+
source: location(rawEntry.loc), routerId: entry.router, routerPath: [...entry.routerPath],
|
|
65
|
+
mountPath: entry.kind === 'event' ? [] : mounts.map(m => m.id), order: entry.index,
|
|
66
|
+
match: available({ method: entry.method || null, path: entry.path ?? null, eventType: entry.eventType || null }),
|
|
67
|
+
control: projectRouterControl(entry, rawEntry, controls.get(entry.stableId)),
|
|
68
|
+
flow: { next: http.get(entry.nextIndex) || null, child: http.get(entry.childStartIndex) || null,
|
|
69
|
+
parentContinue: http.get(entry.parentContinueIndex) || null },
|
|
70
|
+
evidence: [{ basis: 'router-topology', producer: 'canonical-router-execution-topology' }]
|
|
71
|
+
};
|
|
72
|
+
if (entry.kind === 'mount' && entry.childStartIndex > entry.index && entry.parentContinueIndex >= entry.childStartIndex) {
|
|
73
|
+
mounts.push({ id: entry.stableId, end: entry.parentContinueIndex });
|
|
74
|
+
}
|
|
75
|
+
return registration;
|
|
76
|
+
});
|
|
77
|
+
const model = sourceModelForRouterRecognition(surfaceFactsForCanonicalRouterOutput(compiled.router)?.recognition);
|
|
78
|
+
const nodeSource = row => span(row.sourceFile.fileName, row.node.getStart(row.sourceFile), row.node.end);
|
|
79
|
+
const useSites = model?.authoredSites.map(row => {
|
|
80
|
+
const recognized = row.recognized, capability = capabilityForHandlerSurface(recognized);
|
|
81
|
+
const provider = recognized.detail?.provider, effect = recognized.detail?.packageEffect;
|
|
82
|
+
const name = provider ? staticString(provider.resource, { unwrap: true }) : effect?.resource?.kind === 'literal' ? effect.resource.value : undefined;
|
|
83
|
+
const binding = bindingOwnership(provider?.providerKind || effect?.providerKind, name,
|
|
84
|
+
Boolean(provider?.resource) && name === undefined || effect?.resource?.kind === 'dynamic');
|
|
85
|
+
return { ownerId: row.ownerId, surfaceId: recognized.surfaceId, operation: recognized.canonicalOperation,
|
|
86
|
+
capability: capability ? available(capability) : unavailable(),
|
|
87
|
+
binding: binding || { state: 'not-applicable', kind: null, canonicalId: null },
|
|
88
|
+
source: nodeSource(row), evidence: [{ basis: 'authored-recognition', producer: 'canonical-handler-surface-authority' }] };
|
|
89
|
+
});
|
|
90
|
+
const calls = new Map();
|
|
91
|
+
let helperCallsComplete = true;
|
|
92
|
+
for (const row of model?.validatedCalls || []) {
|
|
93
|
+
const source = nodeSource(row);
|
|
94
|
+
// No lowered offsets, call order or guessed identities stand in for source.
|
|
95
|
+
if (source.state !== 'available') { helperCallsComplete = false; continue; }
|
|
96
|
+
const key = JSON.stringify([row.callerId, row.calleeId, source.value.file, source.value.start.offset, source.value.end.offset]);
|
|
97
|
+
if (!calls.has(key)) calls.set(key, { canonicalId: null, callerId: row.callerId, calleeId: row.calleeId,
|
|
98
|
+
registrationIds: [], source, evidence: [{ basis: 'validated-helper-call', producer: 'canonical-router-handler-validation' }] });
|
|
99
|
+
calls.get(key).registrationIds.push(row.registrationId);
|
|
100
|
+
}
|
|
101
|
+
return freeze({ version: 'pulse.compiler-report-topology.v1', handlers, registrations,
|
|
102
|
+
...(model ? { useSites, helperCalls: [...calls.values()], helperCallsComplete } : {}) });
|
|
103
|
+
}
|
|
104
|
+
module.exports = { collectHandlerTopology };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { bindingOwnership } = require('./spine/binding-ownership');
|
|
4
|
+
const { packageOperationRecognitionForCompiled } = require('./spine/package-operation-seam');
|
|
5
|
+
|
|
6
|
+
// Build-time projection of compiler facts. This record never enters the Native
|
|
7
|
+
// plan, executable source, recipe or provider configuration. Unknown expressions
|
|
8
|
+
// are not evaluated or serialized, and reference resolution is not host binding.
|
|
9
|
+
function collectCanonicalReportReferences(compiled, plan) {
|
|
10
|
+
const raw = compiled.metadata.applicationEntries || plan.routing?.entries || [];
|
|
11
|
+
const entries = new Set(raw.map(row => row.stableId));
|
|
12
|
+
if (!raw.length) entries.add('default');
|
|
13
|
+
const helpers = new Map(), rows = new Map();
|
|
14
|
+
const schemas = new Set((compiled.schema?.registry || plan.schemas?.registry)?.schemas?.map(row => row.id) || []);
|
|
15
|
+
const unique = values => [...new Set(values)].sort();
|
|
16
|
+
function calls(value, stageConsumers) {
|
|
17
|
+
if (!value || typeof value !== 'object') return;
|
|
18
|
+
if (value.kind === 'helper-call' || value.kind === 'pure-helper-call') {
|
|
19
|
+
if (!helpers.has(value.helperId)) helpers.set(value.helperId, new Set());
|
|
20
|
+
for (const entry of stageConsumers || [value.callerEntryId]) helpers.get(value.helperId).add(entry);
|
|
21
|
+
}
|
|
22
|
+
for (const child of Object.values(value)) if (child && typeof child === 'object') {
|
|
23
|
+
if (Array.isArray(child)) child.forEach(row => calls(row, stageConsumers)); else calls(child, stageConsumers);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const body of [plan.entry.body, ...(plan.handlers || []).map(row => row.body)]) calls(body);
|
|
27
|
+
for (const stage of plan.stages || []) calls(stage.body, stage.registrations.map(row => row.entryId));
|
|
28
|
+
// Package provenance comes from the trusted recognition bundle, never a file
|
|
29
|
+
// name, schema name or usage-string guess. Keep it outside executable metadata.
|
|
30
|
+
const packageSources = new Map();
|
|
31
|
+
const referenceKey = row => JSON.stringify([row.id, row.usage, row.capability,
|
|
32
|
+
row.file || compiled.metadata.file || '', row.position?.offset]);
|
|
33
|
+
for (const source of packageOperationRecognitionForCompiled(compiled)?.publicExtensions?.plans || []) {
|
|
34
|
+
for (const reference of source.schemaReferences || []) {
|
|
35
|
+
const key = referenceKey(reference);
|
|
36
|
+
if (!packageSources.has(key)) packageSources.set(key, new Set());
|
|
37
|
+
packageSources.get(key).add(source.package);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function owners(effect) {
|
|
41
|
+
if (effect.helperId) return [...(helpers.get(effect.helperId) || [])];
|
|
42
|
+
const entry = effect.applicationEntryStableId || effect.routerEntryStableId;
|
|
43
|
+
return entry ? [entry] : raw.length ? [] : ['default'];
|
|
44
|
+
}
|
|
45
|
+
function add(kind, canonicalId, bindingKind, consumers, observationId, detail = {}) {
|
|
46
|
+
const state = canonicalId === null ? detail.dynamic ? 'dynamic' : 'unknown' : detail.unresolved ? 'unknown' : 'resolved';
|
|
47
|
+
const key = JSON.stringify([kind, bindingKind, state, state === 'resolved' ? canonicalId : observationId]);
|
|
48
|
+
let row = rows.get(key);
|
|
49
|
+
if (!row) {
|
|
50
|
+
row = { kind, canonicalId, bindingKind, state, observationId: state === 'resolved' ? null : observationId,
|
|
51
|
+
entryIds: [], entriesComplete: true, ...(detail.resource ? { resource: detail.resource } : {}) };
|
|
52
|
+
rows.set(key, row);
|
|
53
|
+
}
|
|
54
|
+
row.entryIds.push(...consumers.filter(id => entries.has(id)));
|
|
55
|
+
row.entriesComplete &&= consumers.length > 0 && consumers.every(id => entries.has(id));
|
|
56
|
+
if (detail.externalPackages?.length) row.externalPackages = unique([...(row.externalPackages || []), ...detail.externalPackages]);
|
|
57
|
+
}
|
|
58
|
+
const literal = value => value?.kind === 'literal' && typeof value.value === 'string' ? value.value : null;
|
|
59
|
+
const input = (effect, name) => effect.inputs?.find(row => row.name === name)?.value;
|
|
60
|
+
const field = (object, name) => object?.kind === 'object'
|
|
61
|
+
? object.entries?.find(row => row.key?.kind === 'literal' && row.key.value === name)?.value : null;
|
|
62
|
+
for (const effect of plan.effects || []) {
|
|
63
|
+
const consumers = owners(effect), kind = effect.providerKind;
|
|
64
|
+
if (['config', 'secret', 'kv', 's3'].includes(kind)) {
|
|
65
|
+
const expression = kind === 's3' ? field(input(effect, 'resource'), 'binding')
|
|
66
|
+
: input(effect, kind === 'kv' ? 'store' : 'name');
|
|
67
|
+
const resolved = literal(expression);
|
|
68
|
+
const name = resolved ?? (effect.resource?.kind === 'literal' && typeof effect.resource.value === 'string' ? effect.resource.value : null);
|
|
69
|
+
const binding = bindingOwnership(kind, name, effect.resource?.kind === 'dynamic' || Boolean(expression && expression.kind !== 'literal'));
|
|
70
|
+
add('binding', binding.canonicalId, binding.kind, consumers, effect.id, { dynamic: binding.state === 'dynamic' });
|
|
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
|
+
const topology = require('./report-handler-topology').collectHandlerTopology(compiled);
|
|
114
|
+
return Object.freeze({ version: 'pulse.compiler-report-references.v1',
|
|
115
|
+
...(topology ? { topology } : {}),
|
|
116
|
+
...(routeBehaviors ? { routeBehaviors } : {}),
|
|
117
|
+
responsePayloads: require('./report-response-payloads').collectResponsePayloads(plan, entries, helpers),
|
|
118
|
+
references: Object.freeze([...rows.values()].map(row => Object.freeze({ ...row, entryIds: Object.freeze(unique(row.entryIds)),
|
|
119
|
+
...(row.externalPackages ? { externalPackages: Object.freeze(row.externalPackages) } : {}),
|
|
120
|
+
...(row.resource ? { resource: Object.freeze(row.resource) } : {}) }))) });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
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 };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Project only accepted canonical operations. No AST text, predicates, provider
|
|
4
|
+
// values or generated positions enter the report, and no runtime IR is changed.
|
|
5
|
+
function projectRouterControl(entry, rawEntry, ir) {
|
|
6
|
+
const unavailable = { state: 'unavailable', value: null, reason: 'not-recorded' };
|
|
7
|
+
const value = { pattern: null, eligibility: Boolean(rawEntry.eligibility),
|
|
8
|
+
normal: false, error: false, terminal: false, basis: 'canonical-router-control' };
|
|
9
|
+
if (entry.kind === 'route' || entry.kind === 'mount' || entry.kind === 'use' && entry.scoped) {
|
|
10
|
+
if (!rawEntry.pattern?.normalized) return unavailable;
|
|
11
|
+
value.pattern = rawEntry.pattern.normalized;
|
|
12
|
+
}
|
|
13
|
+
if (entry.kind === 'mount' || !entry.handlerId) value.normal = true;
|
|
14
|
+
else {
|
|
15
|
+
if (!ir?.body) return unavailable;
|
|
16
|
+
let known = true;
|
|
17
|
+
function visit(operation) {
|
|
18
|
+
if (!operation) return;
|
|
19
|
+
switch (operation.kind) {
|
|
20
|
+
case 'block': operation.statements.forEach(visit); break;
|
|
21
|
+
case 'if': visit(operation.thenOperation); visit(operation.elseOperation); break;
|
|
22
|
+
case 'router-guard': visit(operation.body); break;
|
|
23
|
+
case 'router-transfer':
|
|
24
|
+
if (operation.clearNormalMode) value.normal = true;
|
|
25
|
+
else if (operation.errorExpression) value.error = true;
|
|
26
|
+
else known = false;
|
|
27
|
+
break;
|
|
28
|
+
case 'source-statement':
|
|
29
|
+
if (operation.role === 'router-return') value.terminal = true;
|
|
30
|
+
else if (operation.role !== 'router-source') known = false;
|
|
31
|
+
break;
|
|
32
|
+
default: known = false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
visit(ir.body);
|
|
36
|
+
if (entry.kind === 'event') value.terminal = true; // Accepted void fallthrough.
|
|
37
|
+
if (!known || !value.normal && !value.error && !value.terminal) return unavailable;
|
|
38
|
+
}
|
|
39
|
+
return { state: 'available', value, reason: null };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { projectRouterControl };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { createHash } = require('node:crypto');
|
|
6
|
+
const reads = new WeakMap(), outputs = new WeakMap();
|
|
7
|
+
const stamp = s => (s.isDirectory() ? [s.dev, s.ino, s.mode] : [s.dev, s.ino, s.size, s.mtimeNs, s.ctimeNs]).map(String).join(':');
|
|
8
|
+
// In-memory read receipts only: never enter the graph, plan or executable hashes.
|
|
9
|
+
function state(file) {
|
|
10
|
+
const chain = []; let directory = path.parse(file).root;
|
|
11
|
+
for (const part of path.relative(directory, file).split(path.sep)) {
|
|
12
|
+
directory = path.join(directory, part); chain.push(stamp(fs.lstatSync(directory, { bigint: true })));
|
|
13
|
+
}
|
|
14
|
+
return { real: fs.realpathSync(file), token: JSON.stringify(chain) };
|
|
15
|
+
}
|
|
16
|
+
function receiptFor(owner) {
|
|
17
|
+
if (!reads.has(owner)) reads.set(owner, { rows: [], error: null, bytes: 0 });
|
|
18
|
+
return reads.get(owner);
|
|
19
|
+
}
|
|
20
|
+
function readSourceInput(owner, file, kind, identity) {
|
|
21
|
+
const receipt = receiptFor(owner);
|
|
22
|
+
let before; try { before = state(file); } catch { receipt.error = 'REPORT_INPUT_UNBOUND'; }
|
|
23
|
+
const source = fs.readFileSync(file, 'utf8'); // Preserve the compiler's ordinary read/error behavior.
|
|
24
|
+
try {
|
|
25
|
+
const after = state(file), bytes = Buffer.byteLength(source);
|
|
26
|
+
if (!before || before.real !== after.real || before.token !== after.token) receipt.error = 'REPORT_CONCURRENT_CHANGE';
|
|
27
|
+
receipt.bytes += bytes;
|
|
28
|
+
if (receipt.rows.length >= 20000 || receipt.bytes > 256 * 1024 * 1024) receipt.error = 'REPORT_LIMIT';
|
|
29
|
+
if (!receipt.error) receipt.rows.push(Object.freeze({ file, ...after, kind, identity, bytes,
|
|
30
|
+
sha256: createHash('sha256').update(source).digest('hex') }));
|
|
31
|
+
} catch { receipt.error = 'REPORT_INPUT_UNBOUND'; }
|
|
32
|
+
return source;
|
|
33
|
+
}
|
|
34
|
+
function missingManifest(owner, file, identity) {
|
|
35
|
+
if (!owner) return;
|
|
36
|
+
const receipt = receiptFor(owner);
|
|
37
|
+
if (receipt.rows.length >= 20000) receipt.error = 'REPORT_LIMIT';
|
|
38
|
+
if (!receipt.error) receipt.rows.push(Object.freeze({ file, real: null, token: 'absent', kind: 'missing-manifest', identity, bytes: 0, sha256: null }));
|
|
39
|
+
}
|
|
40
|
+
function sourceResolution(owner, file, real, identity) {
|
|
41
|
+
if (!owner || file === real) return;
|
|
42
|
+
const receipt = receiptFor(owner);
|
|
43
|
+
try {
|
|
44
|
+
const observed = state(file);
|
|
45
|
+
if (observed.real !== real) receipt.error = 'REPORT_CONCURRENT_CHANGE';
|
|
46
|
+
if (receipt.rows.length >= 20000) receipt.error = 'REPORT_LIMIT';
|
|
47
|
+
if (!receipt.error) receipt.rows.push(Object.freeze({ file, ...observed, kind: 'source-link', identity, bytes: 0, sha256: null }));
|
|
48
|
+
} catch { receipt.error = 'REPORT_INPUT_UNBOUND'; }
|
|
49
|
+
}
|
|
50
|
+
function retainSourceInputs(compiled, context) {
|
|
51
|
+
const receipt = reads.get(context);
|
|
52
|
+
outputs.set(compiled, Object.freeze({ rows: Object.freeze([...(receipt?.rows || [])]), error: receipt?.error || null }));
|
|
53
|
+
}
|
|
54
|
+
function sourceInputsForCompiled(compiled) { return outputs.get(compiled); }
|
|
55
|
+
module.exports = { readSourceInput, missingManifest, sourceResolution, retainSourceInputs, sourceInputsForCompiled };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// A named binding is compiler evidence, independent of a host's configured
|
|
4
|
+
// value. Both authored recognition and lowered-reference projection use this
|
|
5
|
+
// state policy; absence of a literal never invents a resolved binding.
|
|
6
|
+
function bindingOwnership(kind, name, dynamic = false) {
|
|
7
|
+
if (!['config', 'secret', 'kv', 's3'].includes(kind)) return undefined;
|
|
8
|
+
const canonicalId = typeof name === 'string' ? name : null;
|
|
9
|
+
return Object.freeze({ kind, canonicalId,
|
|
10
|
+
state: canonicalId !== null ? 'resolved' : dynamic ? 'dynamic' : 'unknown' });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { bindingOwnership };
|
|
@@ -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,
|
|
@@ -469,6 +469,25 @@ function routerHandlerNames(entry) {
|
|
|
469
469
|
});
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
+
// Shared compiler projection. Recognition details own provider/package capability
|
|
473
|
+
// names; synchronous surfaces use the same mapping as router analysis.
|
|
474
|
+
function capabilityForHandlerSurface(fact) {
|
|
475
|
+
if (fact.detail?.provider) return fact.detail.provider.capability;
|
|
476
|
+
if (fact.detail?.packageEffect) return String(fact.detail.packageEffect.capability || fact.detail.packageEffect.kind);
|
|
477
|
+
const id = fact.surfaceId;
|
|
478
|
+
if (id === 'ctx.req.body') return 'request.body.forward';
|
|
479
|
+
if (id.startsWith('ctx.fetch.')) return 'fetch';
|
|
480
|
+
if (id.startsWith('ctx.req.')) return id.slice(4);
|
|
481
|
+
if (id.startsWith('ctx.config.')) return 'config.get';
|
|
482
|
+
if (id.startsWith('ctx.secret.')) return 'secret.get';
|
|
483
|
+
if (id.startsWith('ctx.kv.') || id.startsWith('ctx.state.')) return id.slice(4);
|
|
484
|
+
if (id === 'ctx.emit') return 'event.emit';
|
|
485
|
+
if (id.startsWith('ctx.log.')) return 'logging';
|
|
486
|
+
if (['ctx.json', 'ctx.text', 'ctx.response'].includes(id)) return `response.${id.slice(4)}`;
|
|
487
|
+
if (id === 'ctx.param') return 'route.param';
|
|
488
|
+
return undefined;
|
|
489
|
+
}
|
|
490
|
+
|
|
472
491
|
function inspectCanonicalRouterSurfaces(topology, options = {}) {
|
|
473
492
|
if (!topology || topology.frontend !== 'canonical-router' || !topology.sourceFile || !Array.isArray(topology.entries)) {
|
|
474
493
|
throw new TypeError('inspectCanonicalRouterSurfaces requires a prepared canonical Router topology.');
|
|
@@ -476,6 +495,47 @@ function inspectCanonicalRouterSurfaces(topology, options = {}) {
|
|
|
476
495
|
const sourceFile = topology.sourceFile;
|
|
477
496
|
const facts = [];
|
|
478
497
|
const surfacesByEntry = new Map();
|
|
498
|
+
// Private observations retain original nodes before normalization. They never
|
|
499
|
+
// enter recognition facts, summaries, executable metadata or identity hashes.
|
|
500
|
+
const authoredSites = [], validatedCalls = [], seenOwners = new Set();
|
|
501
|
+
function retainAuthored(ownerId, fn, file, names) {
|
|
502
|
+
if (seenOwners.has(ownerId)) return;
|
|
503
|
+
seenOwners.add(ownerId);
|
|
504
|
+
const seenNodes = new WeakSet();
|
|
505
|
+
function visit(node, aliases) {
|
|
506
|
+
if (seenNodes.has(node)) return;
|
|
507
|
+
seenNodes.add(node);
|
|
508
|
+
if (ts.isTypeNode(node) || ts.isFunctionLike(node)) return;
|
|
509
|
+
if (ts.isBlock(node)) {
|
|
510
|
+
const local = new Map(aliases);
|
|
511
|
+
for (const statement of node.statements) {
|
|
512
|
+
const namespace = extractKvNamespaceDeclaration(statement, names.ctxName, { unwrap: true });
|
|
513
|
+
if (namespace) local.set(namespace.variableName, namespace.store);
|
|
514
|
+
visit(statement, local);
|
|
515
|
+
}
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
// Await/parentheses are syntax wrappers, not additional operation sites.
|
|
519
|
+
const current = expressionFor(node, { unwrap: true });
|
|
520
|
+
if (current !== node) { visit(current, aliases); return; }
|
|
521
|
+
if (ts.isAwaitExpression(node)) { visit(node.expression, aliases); return; }
|
|
522
|
+
const recognized = recognizeHandlerSurface(node, { ...names, unwrap: true,
|
|
523
|
+
kvAliases: aliases, packageEffectForCall: options.packageEffectForCall });
|
|
524
|
+
if (recognized && recognized.class !== 'handler-wrapper') {
|
|
525
|
+
authoredSites.push(Object.freeze({ ownerId, sourceFile: file, node, recognized }));
|
|
526
|
+
if (ts.isCallExpression(node)) {
|
|
527
|
+
for (const argument of node.arguments) visit(argument, aliases);
|
|
528
|
+
// A fetch projection owns its fetch receiver; provider receiver inputs
|
|
529
|
+
// can themselves contain synchronous context operations.
|
|
530
|
+
if (recognized.detail?.provider?.resource) visit(recognized.detail.provider.resource, aliases);
|
|
531
|
+
for (const argument of recognized.detail?.fetch?.fetchCall?.arguments || []) visit(argument, aliases);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
ts.forEachChild(node, child => visit(child, aliases));
|
|
536
|
+
}
|
|
537
|
+
if (fn.body) visit(fn.body, new Map());
|
|
538
|
+
}
|
|
479
539
|
|
|
480
540
|
function record(entry, node, names) {
|
|
481
541
|
const recognized = recognizeHandlerSurface(node, {
|
|
@@ -514,6 +574,7 @@ function inspectCanonicalRouterSurfaces(topology, options = {}) {
|
|
|
514
574
|
for (const entry of topology.entries) {
|
|
515
575
|
if (!entry.functionNode) continue;
|
|
516
576
|
const names = routerHandlerNames(entry);
|
|
577
|
+
retainAuthored(entry.entry.handlerId, entry.functionNode, entry.sourceFile || sourceFile, names);
|
|
517
578
|
record(entry, entry.functionNode, names);
|
|
518
579
|
function visit(node) {
|
|
519
580
|
if (typeof ts.isTypeNode === 'function' && ts.isTypeNode(node)) return;
|
|
@@ -528,6 +589,9 @@ function inspectCanonicalRouterSurfaces(topology, options = {}) {
|
|
|
528
589
|
if (entry.functionNode.body) visit(entry.functionNode.body);
|
|
529
590
|
}
|
|
530
591
|
|
|
592
|
+
for (const helper of options.linkedProjectModules?.sourceHelpers || []) {
|
|
593
|
+
if (!helper.pure) retainAuthored(helper.id, helper.functionNode, helper.sourceFile, { ctxName: 'ctx' });
|
|
594
|
+
}
|
|
531
595
|
const recognition = Object.freeze({
|
|
532
596
|
version: HANDLER_SURFACE_AUTHORITY_VERSION,
|
|
533
597
|
contractVersion: contract.HANDLER_SURFACE_CONTRACT_VERSION,
|
|
@@ -543,6 +607,12 @@ function inspectCanonicalRouterSurfaces(topology, options = {}) {
|
|
|
543
607
|
});
|
|
544
608
|
canonicalRouterModels.set(recognition, Object.freeze({
|
|
545
609
|
topology,
|
|
610
|
+
authoredSites: Object.freeze(authoredSites),
|
|
611
|
+
get validatedCalls() { return Object.freeze([...validatedCalls]); },
|
|
612
|
+
retainValidatedCall(entry, helper, node, sourceFile) {
|
|
613
|
+
validatedCalls.push(Object.freeze({ callerId: entry.handlerId, calleeId: helper.id,
|
|
614
|
+
registrationId: entry.stableId, node: ts.getOriginalNode(node), sourceFile }));
|
|
615
|
+
},
|
|
546
616
|
namesForEntry(entryStableId) {
|
|
547
617
|
const entry = topology.entries.find((candidate) => candidate.entry.stableId === entryStableId);
|
|
548
618
|
return entry ? routerHandlerNames(entry) : undefined;
|
|
@@ -587,6 +657,7 @@ module.exports = Object.freeze({
|
|
|
587
657
|
staticString,
|
|
588
658
|
recognizeManagedHandlerWrapper,
|
|
589
659
|
recognizeHandlerSurface,
|
|
660
|
+
capabilityForHandlerSurface,
|
|
590
661
|
classifyRecognizedHandlerSurface,
|
|
591
662
|
extractFetchChain,
|
|
592
663
|
extractEventEmitCall,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const crypto = require('node:crypto');
|
|
4
|
+
const { isTrustedLowerer } = require('@pulse-compute/wasm-contracts/library/manifest');
|
|
4
5
|
const fs = require('node:fs');
|
|
5
6
|
const { createRequire } = require('node:module');
|
|
6
7
|
const path = require('node:path');
|
|
@@ -82,7 +83,7 @@ function catalogDescriptor(record, productRecord) {
|
|
|
82
83
|
const nativeSupported = Boolean(
|
|
83
84
|
product
|
|
84
85
|
&& ['supported', 'provider-dependent'].includes(product.targets.native.status)
|
|
85
|
-
&& compiler.trust
|
|
86
|
+
&& isTrustedLowerer(compiler.trust)
|
|
86
87
|
&& validation.status === 'ok'
|
|
87
88
|
&& wasm.mode !== 'lowering-plan-only'
|
|
88
89
|
&& wasm.sidecar
|
|
@@ -130,11 +131,13 @@ function discoverPackageContractCatalog(options = {}) {
|
|
|
130
131
|
const records = discoverLowerableLibraryManifests({
|
|
131
132
|
cwd,
|
|
132
133
|
workspaceRoot: options.workspaceRoot,
|
|
134
|
+
packageDirs: options.packageDirs,
|
|
133
135
|
scanNodeModules: options.scanNodeModules !== false
|
|
134
136
|
});
|
|
135
137
|
const productCatalog = discoverPulsePackageContracts({
|
|
136
138
|
cwd,
|
|
137
139
|
workspaceRoot: options.workspaceRoot,
|
|
140
|
+
packageDirs: options.packageDirs,
|
|
138
141
|
scanNodeModules: options.scanNodeModules !== false
|
|
139
142
|
});
|
|
140
143
|
const productsByContract = new Map(productCatalog.contracts.map((entry) => [entry.contractId, { contract: entry, metadataHash: entry.metadataHash }]));
|
|
@@ -168,7 +171,7 @@ function discoverPackageContractCatalog(options = {}) {
|
|
|
168
171
|
}),
|
|
169
172
|
contracts: Object.freeze(contracts),
|
|
170
173
|
policy: Object.freeze({
|
|
171
|
-
|
|
174
|
+
trustedBuildDependencies: true,
|
|
172
175
|
sourceSubstringSelection: false,
|
|
173
176
|
graphReachabilitySelection: true,
|
|
174
177
|
publicRegistration: false,
|
|
@@ -333,6 +336,10 @@ function canonicalRealizationArtifact(artifact, record) {
|
|
|
333
336
|
{ id, version, kind, mediaType, contractId, package: packageName }
|
|
334
337
|
);
|
|
335
338
|
}
|
|
339
|
+
if (kind === 'package-runtime-wasm') {
|
|
340
|
+
try { return require('@pulse-compute/wasm-contracts/package/runtime-binding').validateRuntimeBinding(value).artifact; }
|
|
341
|
+
catch { throw new PackageOperationSeamError('PULSE_PACKAGE_REALIZATION_ARTIFACT_INVALID', 'Package runtime Wasm must match the supported bounded ABI.', { id }); }
|
|
342
|
+
}
|
|
336
343
|
return deepFreeze(value);
|
|
337
344
|
}
|
|
338
345
|
|
|
@@ -615,6 +622,7 @@ function recognizeProjectPackageOperations(entryFile, options = {}) {
|
|
|
615
622
|
const manifestRecords = discoverLowerableLibraryManifests({
|
|
616
623
|
cwd: rootDir,
|
|
617
624
|
workspaceRoot: options.workspaceRoot,
|
|
625
|
+
packageDirs: options.packageDirs,
|
|
618
626
|
scanNodeModules: true
|
|
619
627
|
});
|
|
620
628
|
const selected = selectPackageRecords(manifestRecords, sourceText, options.selectedContracts);
|
|
@@ -638,8 +646,10 @@ function recognizeProjectPackageOperations(entryFile, options = {}) {
|
|
|
638
646
|
// This source-aware step is package recognition and validation. The later
|
|
639
647
|
// package-lowering phase receives only the canonical operations below.
|
|
640
648
|
const result = buildPackageOwnedLoweringPlan({
|
|
649
|
+
typescript: ts,
|
|
641
650
|
cwd: rootDir,
|
|
642
651
|
workspaceRoot: options.workspaceRoot,
|
|
652
|
+
packageDirs: options.packageDirs,
|
|
643
653
|
manifestRecords,
|
|
644
654
|
contractId: record.manifest.contractId,
|
|
645
655
|
sourcePath: sourceName,
|
|
@@ -666,8 +676,12 @@ function recognizeProjectPackageOperations(entryFile, options = {}) {
|
|
|
666
676
|
.map(canonicalPackageSchemaReference)
|
|
667
677
|
.map((reference) => schemaReferenceWithSourceFile(reference, sourceName));
|
|
668
678
|
schemaReferences.push(...recognizedSchemaReferences);
|
|
669
|
-
|
|
670
|
-
|
|
679
|
+
const runtimeArtifacts = (contributions.realizationArtifacts || []).map(artifact => canonicalRealizationArtifact(artifact, record));
|
|
680
|
+
for (const effect of canonicalEffects.filter(effect => effect.kind === 'package.invoke')) {
|
|
681
|
+
try { require('@pulse-compute/wasm-contracts/package/runtime-binding').validateRuntimeInvocation(effect, runtimeArtifacts); }
|
|
682
|
+
catch { throw new PackageOperationSeamError('PULSE_PACKAGE_REALIZATION_ARTIFACT_INVALID', 'Package invocation requires an exact declared static runtime binding.', { contractId: record.manifest.contractId }); }
|
|
683
|
+
}
|
|
684
|
+
realizationArtifacts.push(...runtimeArtifacts);
|
|
671
685
|
inspectionArtifacts.push(...(contributions.inspectionArtifacts || [])
|
|
672
686
|
.map((artifact) => canonicalInspectionArtifact(artifact, record)));
|
|
673
687
|
managedHandlers.push(...(contributions.managedHandlers || [])
|
|
@@ -736,7 +750,7 @@ function recognizeProjectPackageOperations(entryFile, options = {}) {
|
|
|
736
750
|
lateLoweringReceivesSourceAst: false,
|
|
737
751
|
lateLoweringReceivesSourceText: false,
|
|
738
752
|
lateLoweringInput: CANONICAL_PACKAGE_OPERATION_VERSION,
|
|
739
|
-
|
|
753
|
+
trustedBuildDependencies: true,
|
|
740
754
|
externalRegistration: false
|
|
741
755
|
})
|
|
742
756
|
});
|
|
@@ -880,7 +894,7 @@ function combinePackageOperationRecognitions(recognitions = []) {
|
|
|
880
894
|
lateLoweringReceivesSourceAst: false,
|
|
881
895
|
lateLoweringReceivesSourceText: false,
|
|
882
896
|
lateLoweringInput: CANONICAL_PACKAGE_OPERATION_VERSION,
|
|
883
|
-
|
|
897
|
+
trustedBuildDependencies: true,
|
|
884
898
|
externalRegistration: false
|
|
885
899
|
})
|
|
886
900
|
});
|
|
@@ -944,7 +958,7 @@ function packageOperationRecognitionFromEffects(effects = []) {
|
|
|
944
958
|
lateLoweringReceivesSourceAst: false,
|
|
945
959
|
lateLoweringReceivesSourceText: false,
|
|
946
960
|
lateLoweringInput: CANONICAL_PACKAGE_OPERATION_VERSION,
|
|
947
|
-
|
|
961
|
+
trustedBuildDependencies: true,
|
|
948
962
|
externalRegistration: false
|
|
949
963
|
})
|
|
950
964
|
});
|