@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.
@@ -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 === 'first-party'
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
- trustedFirstPartyOnly: true,
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
- realizationArtifacts.push(...(contributions.realizationArtifacts || [])
670
- .map((artifact) => canonicalRealizationArtifact(artifact, record)));
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
- firstPartyBuildersOnly: true,
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
- firstPartyBuildersOnly: true,
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
- firstPartyBuildersOnly: true,
961
+ trustedBuildDependencies: true,
948
962
  externalRegistration: false
949
963
  })
950
964
  });