@pulse-compute/wasm-library-kit 0.0.0 → 1.0.0-beta.1
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 +42 -1
- package/package.json +34 -6
- package/src/compiler/assets-lowering.js +160 -0
- package/src/compiler/handler-library-contracts.js +752 -0
- package/src/compiler/host-capabilities.js +297 -0
- package/src/compiler/index.js +30 -0
- package/src/compiler/library-sidecars.js +522 -0
- package/src/compiler/package-contracts.js +243 -0
- package/src/compiler/package-lowering.js +47 -0
- package/src/index.js +8 -0
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = loadContractsDiagnostics();
|
|
7
|
+
const {
|
|
8
|
+
HANDLER_LIBRARY_CONTRACTS_VERSION,
|
|
9
|
+
HANDLER_LIBRARY_CONTRACTS_PHASE,
|
|
10
|
+
HANDLER_EXECUTION_MODES_VERSION,
|
|
11
|
+
LIBRARY_CONTRACT_SCHEMA_VERSION,
|
|
12
|
+
LIBRARY_CONTRACT_VERSION,
|
|
13
|
+
LIBRARY_CAPABILITIES_VERSION,
|
|
14
|
+
LIBRARY_COMPATIBILITY_REPORT_VERSION,
|
|
15
|
+
ALLOWED_HANDLER_EXECUTION_MODES,
|
|
16
|
+
ALLOWED_LIBRARY_MODES,
|
|
17
|
+
ALLOWED_HOST_CAPABILITIES,
|
|
18
|
+
sortedUnique,
|
|
19
|
+
defaultLibraryContracts,
|
|
20
|
+
libraryContractFromLowerableManifest,
|
|
21
|
+
validateLibraryContract
|
|
22
|
+
} = loadContractsLibraryContracts();
|
|
23
|
+
const {
|
|
24
|
+
LOWERABLE_LIBRARY_MANIFEST_VERSION,
|
|
25
|
+
validateLowerableLibraryManifest
|
|
26
|
+
} = loadContractsLibraryManifest();
|
|
27
|
+
const {
|
|
28
|
+
PACKAGE_BUILDER_INVOCATION_VERSION,
|
|
29
|
+
normalizePackageBuilderInvocation,
|
|
30
|
+
normalizePackageBuilderResult
|
|
31
|
+
} = loadContractsPackageContract();
|
|
32
|
+
|
|
33
|
+
const PHASE = HANDLER_LIBRARY_CONTRACTS_PHASE;
|
|
34
|
+
|
|
35
|
+
function loadContractsDiagnostics() {
|
|
36
|
+
try {
|
|
37
|
+
return require('@pulse-compute/wasm-contracts/diagnostics');
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
40
|
+
return require('../../../contracts/src/diagnostics.js');
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function loadContractsLibraryContracts() {
|
|
47
|
+
try {
|
|
48
|
+
return require('@pulse-compute/wasm-contracts/library/contracts');
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
51
|
+
return require('../../../contracts/src/library/contracts.js');
|
|
52
|
+
}
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
function loadContractsLibraryManifest() {
|
|
59
|
+
try {
|
|
60
|
+
return require('@pulse-compute/wasm-contracts/library/manifest');
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
63
|
+
return require('../../../contracts/src/library/manifest.js');
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function loadContractsPackageContract() {
|
|
70
|
+
try {
|
|
71
|
+
return require('@pulse-compute/wasm-contracts/package/package-contract');
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
74
|
+
return require('../../../contracts/src/package/package-contract.js');
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function loadAssetsContracts() {
|
|
81
|
+
try {
|
|
82
|
+
return require('@pulse-compute/wasm-contracts/assets/contracts');
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
85
|
+
return require('../../../contracts/src/assets/contracts.js');
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
function loadGripContracts() {
|
|
95
|
+
try {
|
|
96
|
+
return require('@pulse-compute/wasm-contracts/grip/contracts');
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
99
|
+
return require('../../../contracts/src/grip/contracts.js');
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readJsonIfExists(file) {
|
|
106
|
+
if (!fs.existsSync(file)) return undefined;
|
|
107
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function findWorkspaceRoot(cwd) {
|
|
111
|
+
let current = path.resolve(cwd || process.cwd());
|
|
112
|
+
while (true) {
|
|
113
|
+
const manifest = readJsonIfExists(path.join(current, 'package.json'));
|
|
114
|
+
if ((manifest && manifest.workspaces) || fs.existsSync(path.join(current, 'pnpm-workspace.yaml'))) return current;
|
|
115
|
+
const parent = path.dirname(current);
|
|
116
|
+
if (parent === current) return path.resolve(cwd || process.cwd());
|
|
117
|
+
current = parent;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function workspacePatterns(root) {
|
|
122
|
+
const manifest = readJsonIfExists(path.join(root, 'package.json')) || {};
|
|
123
|
+
if (Array.isArray(manifest.workspaces)) return manifest.workspaces;
|
|
124
|
+
if (manifest.workspaces && Array.isArray(manifest.workspaces.packages)) return manifest.workspaces.packages;
|
|
125
|
+
const pnpmWorkspace = path.join(root, 'pnpm-workspace.yaml');
|
|
126
|
+
if (fs.existsSync(pnpmWorkspace)) {
|
|
127
|
+
return fs.readFileSync(pnpmWorkspace, 'utf8')
|
|
128
|
+
.split(/\r?\n/)
|
|
129
|
+
.map((line) => line.trim().replace(/^-\s*/, '').replace(/^['"]|['"]$/g, ''))
|
|
130
|
+
.filter((line) => line && !line.startsWith('#'));
|
|
131
|
+
}
|
|
132
|
+
return ['packages/*'];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function expandWorkspacePattern(root, pattern) {
|
|
136
|
+
const normalized = String(pattern || '').replace(/\\/g, '/');
|
|
137
|
+
if (!normalized.endsWith('/*')) return [];
|
|
138
|
+
const base = path.join(root, normalized.slice(0, -2));
|
|
139
|
+
if (!fs.existsSync(base)) return [];
|
|
140
|
+
return fs.readdirSync(base, { withFileTypes: true })
|
|
141
|
+
.filter((entry) => entry.isDirectory())
|
|
142
|
+
.map((entry) => path.join(base, entry.name));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
function candidateNodeModulesRoots(root, cwd) {
|
|
147
|
+
const roots = [];
|
|
148
|
+
const seen = new Set();
|
|
149
|
+
function add(dir) {
|
|
150
|
+
const resolved = path.resolve(dir);
|
|
151
|
+
if (!seen.has(resolved)) {
|
|
152
|
+
seen.add(resolved);
|
|
153
|
+
roots.push(resolved);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
add(path.join(root, 'node_modules'));
|
|
157
|
+
let current = path.resolve(cwd || root || process.cwd());
|
|
158
|
+
const stop = path.dirname(path.resolve(root || current));
|
|
159
|
+
while (true) {
|
|
160
|
+
add(path.join(current, 'node_modules'));
|
|
161
|
+
if (current === path.resolve(root || current)) break;
|
|
162
|
+
const parent = path.dirname(current);
|
|
163
|
+
if (parent === current || parent === stop) break;
|
|
164
|
+
current = parent;
|
|
165
|
+
}
|
|
166
|
+
return roots;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function nodeModulesPackageDirs(nodeModulesRoot) {
|
|
170
|
+
if (!fs.existsSync(nodeModulesRoot)) return [];
|
|
171
|
+
const out = [];
|
|
172
|
+
for (const entry of fs.readdirSync(nodeModulesRoot, { withFileTypes: true })) {
|
|
173
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
174
|
+
if (entry.name.startsWith('.')) continue;
|
|
175
|
+
const full = path.join(nodeModulesRoot, entry.name);
|
|
176
|
+
if (entry.name.startsWith('@')) {
|
|
177
|
+
if (!fs.existsSync(full)) continue;
|
|
178
|
+
for (const scoped of fs.readdirSync(full, { withFileTypes: true })) {
|
|
179
|
+
if (!scoped.isDirectory() && !scoped.isSymbolicLink()) continue;
|
|
180
|
+
if (scoped.name.startsWith('.')) continue;
|
|
181
|
+
out.push(path.join(full, scoped.name));
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
out.push(full);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function packageDirsFromNodeModules(root, cwd) {
|
|
191
|
+
return candidateNodeModulesRoots(root, cwd).flatMap((nodeModulesRoot) => nodeModulesPackageDirs(nodeModulesRoot));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function packageManifestPath(packageJson) {
|
|
195
|
+
const pulsewasm = packageJson && packageJson.pulsewasm;
|
|
196
|
+
if (!pulsewasm || typeof pulsewasm !== 'object') return undefined;
|
|
197
|
+
if (typeof pulsewasm.manifest === 'string') return pulsewasm.manifest;
|
|
198
|
+
if (typeof pulsewasm.lowerableManifest === 'string') return pulsewasm.lowerableManifest;
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function loadManifestFile(file) {
|
|
203
|
+
delete require.cache[require.resolve(file)];
|
|
204
|
+
const loaded = require(file);
|
|
205
|
+
return loaded && loaded.pulseWasmManifest ? loaded.pulseWasmManifest : loaded;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function makeLowerableBuilderDiagnostic(code, message, hint, details) {
|
|
209
|
+
return normalizeDiagnostic({
|
|
210
|
+
phase: PHASE,
|
|
211
|
+
severity: 'error',
|
|
212
|
+
code,
|
|
213
|
+
message,
|
|
214
|
+
hint,
|
|
215
|
+
details,
|
|
216
|
+
loc: { file: '<lowerable-compiler-builder>' }
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function resolveLowerableCompilerBuilder(record) {
|
|
221
|
+
const manifest = record && record.manifest ? record.manifest : {};
|
|
222
|
+
const compiler = manifest.compiler && typeof manifest.compiler === 'object' ? manifest.compiler : {};
|
|
223
|
+
const entry = compiler.entry;
|
|
224
|
+
const exportName = compiler.export;
|
|
225
|
+
const diagnostics = [];
|
|
226
|
+
|
|
227
|
+
if (compiler.trust !== 'first-party') {
|
|
228
|
+
diagnostics.push(makeLowerableBuilderDiagnostic(
|
|
229
|
+
'PULSEWASM_LOWERABLE_COMPILER_BUILDER_TRUST_REQUIRED',
|
|
230
|
+
`Lowerable compiler builder for ${manifest.contractId || '<unknown>'} must declare trust "first-party".`,
|
|
231
|
+
'Pass 46 only executes first-party package-owned builders; arbitrary third-party lowerers remain disabled.',
|
|
232
|
+
{ contractId: manifest.contractId, trust: compiler.trust }
|
|
233
|
+
));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!entry || typeof entry !== 'string') {
|
|
237
|
+
diagnostics.push(makeLowerableBuilderDiagnostic(
|
|
238
|
+
'PULSEWASM_LOWERABLE_COMPILER_BUILDER_ENTRY_REQUIRED',
|
|
239
|
+
`Lowerable compiler builder for ${manifest.contractId || '<unknown>'} must declare compiler.entry.`,
|
|
240
|
+
'Add compiler.entry to the package-owned PulseWasm manifest.',
|
|
241
|
+
{ contractId: manifest.contractId, compiler }
|
|
242
|
+
));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!exportName || typeof exportName !== 'string') {
|
|
246
|
+
diagnostics.push(makeLowerableBuilderDiagnostic(
|
|
247
|
+
'PULSEWASM_LOWERABLE_COMPILER_BUILDER_EXPORT_REQUIRED',
|
|
248
|
+
`Lowerable compiler builder for ${manifest.contractId || '<unknown>'} must declare compiler.export.`,
|
|
249
|
+
'Add compiler.export to the package-owned PulseWasm manifest.',
|
|
250
|
+
{ contractId: manifest.contractId, compiler }
|
|
251
|
+
));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
status: diagnostics.length === 0 ? 'ok' : 'error',
|
|
256
|
+
packageDir: record && record.packageDir,
|
|
257
|
+
packageName: record && record.packageName,
|
|
258
|
+
manifest,
|
|
259
|
+
compiler,
|
|
260
|
+
entry: entry && record && record.packageDir ? path.resolve(record.packageDir, entry) : undefined,
|
|
261
|
+
exportName,
|
|
262
|
+
diagnostics
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function loadLowerableCompilerBuilder(record) {
|
|
267
|
+
const resolution = resolveLowerableCompilerBuilder(record);
|
|
268
|
+
if (resolution.status !== 'ok') {
|
|
269
|
+
const error = new Error(resolution.diagnostics[0].message);
|
|
270
|
+
error.diagnostics = resolution.diagnostics;
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
delete require.cache[require.resolve(resolution.entry)];
|
|
275
|
+
const loaded = require(resolution.entry);
|
|
276
|
+
const builder = loaded && loaded[resolution.exportName];
|
|
277
|
+
if (typeof builder !== 'function') {
|
|
278
|
+
const diagnostic = makeLowerableBuilderDiagnostic(
|
|
279
|
+
'PULSEWASM_LOWERABLE_COMPILER_BUILDER_EXPORT_NOT_FOUND',
|
|
280
|
+
`Lowerable compiler builder ${resolution.exportName} was not exported by ${resolution.entry}.`,
|
|
281
|
+
'Export the package-owned builder function named by compiler.export.',
|
|
282
|
+
{ entry: resolution.entry, exportName: resolution.exportName }
|
|
283
|
+
);
|
|
284
|
+
const error = new Error(diagnostic.message);
|
|
285
|
+
error.diagnostics = [diagnostic];
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
...resolution,
|
|
291
|
+
builder,
|
|
292
|
+
module: loaded
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function packageBuilderAuthority(record) {
|
|
297
|
+
const manifest = record && record.manifest || {};
|
|
298
|
+
return Object.freeze({
|
|
299
|
+
contractId: manifest.contractId,
|
|
300
|
+
npmPackage: manifest.npmPackage,
|
|
301
|
+
lowerableSubpath: manifest.lowerableSubpath
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function sourceTextForInvocation(inputs, sourceFile) {
|
|
306
|
+
if (inputs.sourceText !== undefined) return String(inputs.sourceText);
|
|
307
|
+
if (sourceFile && typeof sourceFile.getFullText === 'function') return sourceFile.getFullText();
|
|
308
|
+
if (sourceFile && typeof sourceFile.text === 'string') return sourceFile.text;
|
|
309
|
+
return '';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function invokeLowerableCompilerBuilder(loaded, record, libraryContracts, inputs = {}) {
|
|
313
|
+
const sourceFile = inputs.sourceFile || null;
|
|
314
|
+
const sourcePath = String(
|
|
315
|
+
inputs.sourcePath
|
|
316
|
+
|| sourceFile && sourceFile.fileName
|
|
317
|
+
|| '<package-lowering-source>'
|
|
318
|
+
).replace(/\\/g, '/');
|
|
319
|
+
const authority = packageBuilderAuthority(record);
|
|
320
|
+
const invocation = normalizePackageBuilderInvocation({
|
|
321
|
+
version: PACKAGE_BUILDER_INVOCATION_VERSION,
|
|
322
|
+
cwd: path.resolve(inputs.cwd || process.cwd()),
|
|
323
|
+
workspaceRoot: inputs.workspaceRoot == null ? null : path.resolve(inputs.workspaceRoot),
|
|
324
|
+
sourcePath,
|
|
325
|
+
sourceText: sourceTextForInvocation(inputs, sourceFile),
|
|
326
|
+
sourceFile,
|
|
327
|
+
typescript: inputs.typescript || inputs.ts || null,
|
|
328
|
+
manifest: record.manifest,
|
|
329
|
+
libraryContracts,
|
|
330
|
+
packageCompilerBuilder: {
|
|
331
|
+
owner: record.manifest && record.manifest.compiler ? record.manifest.compiler.builderOwner : record.packageName,
|
|
332
|
+
entry: loaded.entry,
|
|
333
|
+
export: loaded.exportName,
|
|
334
|
+
trust: loaded.compiler && loaded.compiler.trust
|
|
335
|
+
},
|
|
336
|
+
generatedBy: inputs.generatedBy || PACKAGE_BUILDER_INVOCATION_VERSION,
|
|
337
|
+
routePlan: inputs.routePlan || null,
|
|
338
|
+
schemaBundle: inputs.schemaBundle || null
|
|
339
|
+
}, authority);
|
|
340
|
+
const result = loaded.builder(invocation);
|
|
341
|
+
return normalizePackageBuilderResult(result, authority);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function discoverLowerableLibraryManifests(inputs = {}) {
|
|
345
|
+
const cwd = inputs.cwd || process.cwd();
|
|
346
|
+
const root = inputs.workspaceRoot || findWorkspaceRoot(cwd);
|
|
347
|
+
const workspaceDirs = workspacePatterns(root).flatMap((pattern) => expandWorkspacePattern(root, pattern));
|
|
348
|
+
const nodeModuleDirs = inputs.scanNodeModules === false ? [] : packageDirsFromNodeModules(root, cwd);
|
|
349
|
+
const packageDirs = [];
|
|
350
|
+
const seenDirs = new Set();
|
|
351
|
+
for (const packageDir of workspaceDirs.concat(nodeModuleDirs)) {
|
|
352
|
+
const resolved = path.resolve(packageDir);
|
|
353
|
+
const identity = fs.existsSync(resolved) ? fs.realpathSync.native(resolved) : resolved;
|
|
354
|
+
if (seenDirs.has(identity)) continue;
|
|
355
|
+
seenDirs.add(identity);
|
|
356
|
+
packageDirs.push(resolved);
|
|
357
|
+
}
|
|
358
|
+
const manifests = [];
|
|
359
|
+
const seenManifests = new Set();
|
|
360
|
+
|
|
361
|
+
for (const packageDir of packageDirs) {
|
|
362
|
+
const packageJsonPath = path.join(packageDir, 'package.json');
|
|
363
|
+
const packageJson = readJsonIfExists(packageJsonPath);
|
|
364
|
+
const manifestRef = packageManifestPath(packageJson);
|
|
365
|
+
if (!manifestRef) continue;
|
|
366
|
+
const manifestFile = path.resolve(packageDir, manifestRef);
|
|
367
|
+
const manifestKey = manifestFile;
|
|
368
|
+
if (seenManifests.has(manifestKey)) continue;
|
|
369
|
+
seenManifests.add(manifestKey);
|
|
370
|
+
const manifest = loadManifestFile(manifestFile);
|
|
371
|
+
const validation = validateLowerableLibraryManifest(manifest);
|
|
372
|
+
manifests.push({
|
|
373
|
+
packageDir,
|
|
374
|
+
packageName: packageJson.name,
|
|
375
|
+
manifestFile,
|
|
376
|
+
manifest,
|
|
377
|
+
validation,
|
|
378
|
+
source: workspaceDirs.includes(packageDir) ? 'workspace' : 'node_modules'
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
manifests.sort((a, b) => String(a.manifest.contractId || a.packageName).localeCompare(String(b.manifest.contractId || b.packageName)));
|
|
383
|
+
return manifests;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function enrichKnownProtocolContract(contract) {
|
|
387
|
+
const assetsContracts = loadAssetsContracts();
|
|
388
|
+
const gripContracts = loadGripContracts();
|
|
389
|
+
if (contract && contract.package === assetsContracts.ASSETS_CONTRACT_ID) {
|
|
390
|
+
const extension = assetsContracts.assetsProtocolExtension();
|
|
391
|
+
return {
|
|
392
|
+
...contract,
|
|
393
|
+
assets: extension.assets,
|
|
394
|
+
policy: {
|
|
395
|
+
...(contract.policy || {}),
|
|
396
|
+
...extension.policy
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
if (contract && contract.package === gripContracts.GRIP_CONTRACT_ID) {
|
|
401
|
+
const extension = gripContracts.gripProtocolExtension();
|
|
402
|
+
return {
|
|
403
|
+
...contract,
|
|
404
|
+
grip: extension.grip,
|
|
405
|
+
policy: {
|
|
406
|
+
...(contract.policy || {}),
|
|
407
|
+
...extension.policy
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return contract;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function lowerableManifestRecordToLibraryContract(record) {
|
|
415
|
+
const contract = libraryContractFromLowerableManifest(record.manifest);
|
|
416
|
+
return enrichKnownProtocolContract(contract);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function resolveDefaultLibraryContracts(inputs = {}) {
|
|
420
|
+
const byPackage = new Map(defaultLibraryContracts().map((contract) => [contract.package, { ...contract, fixture: true }]));
|
|
421
|
+
for (const record of discoverLowerableLibraryManifests(inputs)) {
|
|
422
|
+
const contract = lowerableManifestRecordToLibraryContract(record);
|
|
423
|
+
byPackage.set(contract.package, {
|
|
424
|
+
...contract,
|
|
425
|
+
fixture: false,
|
|
426
|
+
supersedesFixture: Boolean(defaultLibraryContracts().some((entry) => entry.package === contract.package))
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return Array.from(byPackage.values()).sort((a, b) => String(a.package).localeCompare(String(b.package)));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function findLowerableManifestRecord(inputs = {}) {
|
|
433
|
+
const manifestRecords = Array.isArray(inputs.manifestRecords) && inputs.manifestRecords.length > 0
|
|
434
|
+
? inputs.manifestRecords
|
|
435
|
+
: discoverLowerableLibraryManifests({ cwd: inputs.cwd || process.cwd(), workspaceRoot: inputs.workspaceRoot, scanNodeModules: inputs.scanNodeModules });
|
|
436
|
+
const explicitManifest = inputs.manifest;
|
|
437
|
+
if (explicitManifest) {
|
|
438
|
+
const record = manifestRecords.find((entry) => entry.manifest === explicitManifest || (
|
|
439
|
+
entry.manifest &&
|
|
440
|
+
entry.manifest.contractId === explicitManifest.contractId &&
|
|
441
|
+
entry.manifest.npmPackage === explicitManifest.npmPackage
|
|
442
|
+
));
|
|
443
|
+
if (record) return { record, manifestRecords };
|
|
444
|
+
}
|
|
445
|
+
const contractId = inputs.contractId;
|
|
446
|
+
const npmPackage = inputs.npmPackage;
|
|
447
|
+
const lowerableSubpath = inputs.lowerableSubpath;
|
|
448
|
+
return {
|
|
449
|
+
record: manifestRecords.find((entry) => {
|
|
450
|
+
const manifest = entry.manifest || {};
|
|
451
|
+
if (contractId && manifest.contractId !== contractId) return false;
|
|
452
|
+
if (npmPackage && manifest.npmPackage !== npmPackage) return false;
|
|
453
|
+
if (lowerableSubpath && manifest.lowerableSubpath !== lowerableSubpath) return false;
|
|
454
|
+
return Boolean(contractId || npmPackage || lowerableSubpath);
|
|
455
|
+
}),
|
|
456
|
+
manifestRecords
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function buildPackageOwnedLoweringPlan(inputs = {}) {
|
|
461
|
+
const cwd = inputs.cwd || process.cwd();
|
|
462
|
+
const { record, manifestRecords } = findLowerableManifestRecord(inputs);
|
|
463
|
+
if (!record) {
|
|
464
|
+
const error = new Error(`Could not discover package-owned lowerable manifest ${inputs.contractId || inputs.npmPackage || inputs.lowerableSubpath || '<unknown>'}.`);
|
|
465
|
+
error.diagnostics = [];
|
|
466
|
+
throw error;
|
|
467
|
+
}
|
|
468
|
+
const loaded = loadLowerableCompilerBuilder(record);
|
|
469
|
+
const libraryContracts = Array.isArray(inputs.libraryContracts) && inputs.libraryContracts.length > 0
|
|
470
|
+
? inputs.libraryContracts
|
|
471
|
+
: resolveDefaultLibraryContracts({ cwd, workspaceRoot: inputs.workspaceRoot, scanNodeModules: inputs.scanNodeModules });
|
|
472
|
+
return invokeLowerableCompilerBuilder(loaded, record, libraryContracts, inputs);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function buildHandlerExecutionModes(inputs = {}) {
|
|
476
|
+
const modes = [
|
|
477
|
+
{
|
|
478
|
+
mode: 'host-imports',
|
|
479
|
+
status: 'implemented-proof-local-node',
|
|
480
|
+
current: true,
|
|
481
|
+
description: 'Wasm routing core calls host JS handlers through deterministic imports. This is the current bridge/local/Node adapter mode.',
|
|
482
|
+
evidence: ['assemblyscript-handlers.json', 'wasm-host-bridge.json', 'host-runtime-kernel.json', 'node-adapter.json'],
|
|
483
|
+
optimizedTarget: false,
|
|
484
|
+
debtBehavior: 'none when selected intentionally for bridge/local mode'
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
mode: 'compiled-wasm',
|
|
488
|
+
status: 'planned-optimized-target',
|
|
489
|
+
current: false,
|
|
490
|
+
description: 'Routing core, user handlers, and compatible library sidecars compile/link into the Wasm artifact.',
|
|
491
|
+
evidence: [],
|
|
492
|
+
optimizedTarget: true,
|
|
493
|
+
requires: ['compiled user handler pipeline', 'library sidecar validation', 'host capability/provider mapping'],
|
|
494
|
+
notImplementedYet: true
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
mode: 'js-engine',
|
|
498
|
+
status: 'manual-escape-hatch',
|
|
499
|
+
current: false,
|
|
500
|
+
description: 'Manual JS engine path for shipping when the Wasm target is not viable. Never automatic fallback.',
|
|
501
|
+
evidence: ['deployment-posture.json'],
|
|
502
|
+
optimizedTarget: false,
|
|
503
|
+
debtBehavior: 'deployment-posture.json records manual escape-hatch debt'
|
|
504
|
+
}
|
|
505
|
+
];
|
|
506
|
+
return {
|
|
507
|
+
version: HANDLER_EXECUTION_MODES_VERSION,
|
|
508
|
+
generatedBy: inputs.generatedBy || PACKAGE_VERSION,
|
|
509
|
+
phase: PHASE,
|
|
510
|
+
status: 'locked',
|
|
511
|
+
policy: {
|
|
512
|
+
noAutomaticFallback: true,
|
|
513
|
+
noModeInference: true,
|
|
514
|
+
hostImportsAreProofAndBridgeMode: true,
|
|
515
|
+
compiledWasmIsOptimizedTarget: true,
|
|
516
|
+
jsEngineIsManualDebt: true
|
|
517
|
+
},
|
|
518
|
+
summary: {
|
|
519
|
+
modes: modes.length,
|
|
520
|
+
currentMode: 'host-imports',
|
|
521
|
+
optimizedTarget: 'compiled-wasm',
|
|
522
|
+
manualEscapeHatch: 'js-engine'
|
|
523
|
+
},
|
|
524
|
+
modes
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function buildLibraryContractSchema(inputs = {}) {
|
|
529
|
+
return {
|
|
530
|
+
version: LIBRARY_CONTRACT_SCHEMA_VERSION,
|
|
531
|
+
generatedBy: inputs.generatedBy || PACKAGE_VERSION,
|
|
532
|
+
phase: PHASE,
|
|
533
|
+
contractVersion: LIBRARY_CONTRACT_VERSION,
|
|
534
|
+
status: 'locked',
|
|
535
|
+
description: 'Schema summary for pulse.library.json. This is a validation artifact, not a public plugin API.',
|
|
536
|
+
required: ['version', 'package', 'modes.typescript.entry', 'modes.jsEngine.entry', 'modes.wasm.sidecar', 'modes.wasm.lowerings'],
|
|
537
|
+
modes: ALLOWED_LIBRARY_MODES.map((mode) => ({ mode })),
|
|
538
|
+
allowedHostCapabilities: ALLOWED_HOST_CAPABILITIES,
|
|
539
|
+
manifestVersion: LOWERABLE_LIBRARY_MANIFEST_VERSION,
|
|
540
|
+
packageIdentity: {
|
|
541
|
+
sourceOfTruth: 'package-owned lowerable manifests discovered from package.json pulsewasm.manifest',
|
|
542
|
+
corePackageRegistry: false
|
|
543
|
+
},
|
|
544
|
+
loweringShape: {
|
|
545
|
+
tsSymbol: 'static imported TS symbol declared by the package-owned manifest',
|
|
546
|
+
asSymbol: 'static AS sidecar symbol declared by the package-owned manifest',
|
|
547
|
+
callShape: 'declared call-shape name; validation remains strict',
|
|
548
|
+
hostCapabilities: 'subset of allowedHostCapabilities'
|
|
549
|
+
},
|
|
550
|
+
policies: {
|
|
551
|
+
contractRequiredForWasm: true,
|
|
552
|
+
sidecarRequiredForWasm: true,
|
|
553
|
+
arbitraryTsCompilationForbidden: true,
|
|
554
|
+
inferredCompatibilityForbidden: true,
|
|
555
|
+
hintsAreContracts: true,
|
|
556
|
+
publicPluginApi: false
|
|
557
|
+
},
|
|
558
|
+
diagnostics: {
|
|
559
|
+
errors: [
|
|
560
|
+
'PULSEWASM_LIBRARY_CONTRACT_VERSION_UNSUPPORTED',
|
|
561
|
+
'PULSEWASM_LIBRARY_WASM_SIDECAR_REQUIRED',
|
|
562
|
+
'PULSEWASM_LIBRARY_LOWERINGS_REQUIRED',
|
|
563
|
+
'PULSEWASM_LIBRARY_UNKNOWN_HOST_CAPABILITY'
|
|
564
|
+
],
|
|
565
|
+
debt: ['PULSEWASM_LIBRARY_REQUIRES_JS_ENGINE']
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function buildLibraryCompatibilityReport(contracts, inputs = {}) {
|
|
571
|
+
const reports = contracts.map((contract) => validateLibraryContract(contract));
|
|
572
|
+
const diagnostics = reports.flatMap((report) => report.diagnostics);
|
|
573
|
+
return {
|
|
574
|
+
version: LIBRARY_COMPATIBILITY_REPORT_VERSION,
|
|
575
|
+
generatedBy: inputs.generatedBy || PACKAGE_VERSION,
|
|
576
|
+
phase: PHASE,
|
|
577
|
+
status: diagnostics.length === 0 ? 'ok' : 'error',
|
|
578
|
+
summary: {
|
|
579
|
+
libraries: reports.length,
|
|
580
|
+
ok: reports.filter((report) => report.status === 'ok').length,
|
|
581
|
+
error: reports.filter((report) => report.status === 'error').length,
|
|
582
|
+
lowerings: reports.reduce((sum, report) => sum + report.lowerings.length, 0),
|
|
583
|
+
hostCapabilities: sortedUnique(reports.flatMap((report) => report.hostCapabilities)).length,
|
|
584
|
+
diagnostics: diagnostics.length
|
|
585
|
+
},
|
|
586
|
+
reports,
|
|
587
|
+
diagnostics
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function buildLibraryCapabilities(contracts, compatibilityReport, inputs = {}) {
|
|
592
|
+
const reports = compatibilityReport.reports || [];
|
|
593
|
+
return {
|
|
594
|
+
version: LIBRARY_CAPABILITIES_VERSION,
|
|
595
|
+
generatedBy: inputs.generatedBy || PACKAGE_VERSION,
|
|
596
|
+
phase: PHASE,
|
|
597
|
+
status: compatibilityReport.status,
|
|
598
|
+
policy: {
|
|
599
|
+
sourceOfTruth: 'pulse.library.json contract manifests',
|
|
600
|
+
noGuessedPackageCompatibility: true,
|
|
601
|
+
sidecarsExplicit: true,
|
|
602
|
+
effectsExplicit: true,
|
|
603
|
+
wasiProviderDeferredTo11E: true,
|
|
604
|
+
effectRuntimeDeferredTo11F: true
|
|
605
|
+
},
|
|
606
|
+
summary: {
|
|
607
|
+
libraries: reports.length,
|
|
608
|
+
wasmSidecars: reports.filter((report) => report.modes.wasmSidecar).length,
|
|
609
|
+
lowerings: reports.reduce((sum, report) => sum + report.lowerings.length, 0),
|
|
610
|
+
requiredHostCapabilities: sortedUnique(reports.flatMap((report) => report.hostCapabilities)).length,
|
|
611
|
+
diagnostics: compatibilityReport.diagnostics.length
|
|
612
|
+
},
|
|
613
|
+
hostCapabilities: sortedUnique(reports.flatMap((report) => report.hostCapabilities)).map((capability) => ({
|
|
614
|
+
capability,
|
|
615
|
+
status: 'declared-required-by-library-contract',
|
|
616
|
+
providerMapping: 'deferred-to-11E'
|
|
617
|
+
})),
|
|
618
|
+
libraries: reports.map((report) => ({
|
|
619
|
+
package: report.package,
|
|
620
|
+
status: report.status,
|
|
621
|
+
modes: report.modes,
|
|
622
|
+
sidecar: report.sidecar,
|
|
623
|
+
hostCapabilities: report.hostCapabilities,
|
|
624
|
+
lowerings: report.lowerings,
|
|
625
|
+
policy: report.policy
|
|
626
|
+
}))
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function markdownForContracts(handlerModes, schema, capabilities, compatibilityReport) {
|
|
631
|
+
const lines = [];
|
|
632
|
+
lines.push('# PulseWasm Phase 11D — Handler Modes + Library Capability Contracts');
|
|
633
|
+
lines.push('');
|
|
634
|
+
lines.push('## Status');
|
|
635
|
+
lines.push('Locked.');
|
|
636
|
+
lines.push('');
|
|
637
|
+
lines.push('## Handler execution modes');
|
|
638
|
+
for (const mode of handlerModes.modes) {
|
|
639
|
+
lines.push(`- **${mode.mode}** — ${mode.status}. ${mode.description}`);
|
|
640
|
+
}
|
|
641
|
+
lines.push('');
|
|
642
|
+
lines.push('## Library compatibility rule');
|
|
643
|
+
lines.push('PulseWasm never guesses that an arbitrary TS package is Wasm-compatible. A Wasm-compatible library must provide a validated `pulse.library.json` contract and an explicit AssemblyScript sidecar.');
|
|
644
|
+
lines.push('');
|
|
645
|
+
lines.push('## Library fixtures validated in 11D');
|
|
646
|
+
for (const library of capabilities.libraries) {
|
|
647
|
+
lines.push(`- **${library.package}** — ${library.status}; sidecar: \`${library.sidecar}\`; capabilities: ${library.hostCapabilities.join(', ') || 'none'}.`);
|
|
648
|
+
}
|
|
649
|
+
lines.push('');
|
|
650
|
+
lines.push('## Contract schema');
|
|
651
|
+
lines.push(`Contract version: \`${schema.contractVersion}\`.`);
|
|
652
|
+
lines.push('Required fields:');
|
|
653
|
+
for (const field of schema.required) lines.push(`- \`${field}\``);
|
|
654
|
+
lines.push('');
|
|
655
|
+
lines.push('## Non-goals');
|
|
656
|
+
lines.push('- no compiled user handlers');
|
|
657
|
+
lines.push('- no sidecar compilation');
|
|
658
|
+
lines.push('- no GRIP or assets implementation');
|
|
659
|
+
lines.push('- no WASI provider mapping');
|
|
660
|
+
lines.push('- no effect runtime');
|
|
661
|
+
lines.push('- no platform target');
|
|
662
|
+
lines.push('');
|
|
663
|
+
lines.push('## Compatibility report');
|
|
664
|
+
lines.push(`Libraries: ${compatibilityReport.summary.libraries}; lowerings: ${compatibilityReport.summary.lowerings}; diagnostics: ${compatibilityReport.summary.diagnostics}.`);
|
|
665
|
+
lines.push('');
|
|
666
|
+
return `${lines.join('\n')}\n`;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function buildHandlerLibraryContracts(inputs = {}) {
|
|
670
|
+
const cwd = inputs.cwd || process.cwd();
|
|
671
|
+
const generatedBy = inputs.generatedBy || PACKAGE_VERSION;
|
|
672
|
+
const contracts = Array.isArray(inputs.libraryContracts) && inputs.libraryContracts.length > 0
|
|
673
|
+
? inputs.libraryContracts
|
|
674
|
+
: resolveDefaultLibraryContracts({ cwd: inputs.cwd || cwd, workspaceRoot: inputs.workspaceRoot });
|
|
675
|
+
|
|
676
|
+
const handlerExecutionModes = normalizeArtifact(buildHandlerExecutionModes({ generatedBy }), cwd);
|
|
677
|
+
const libraryContractSchema = normalizeArtifact(buildLibraryContractSchema({ generatedBy }), cwd);
|
|
678
|
+
const libraryCompatibilityReport = normalizeArtifact(buildLibraryCompatibilityReport(contracts, { generatedBy }), cwd);
|
|
679
|
+
const libraryCapabilities = normalizeArtifact(buildLibraryCapabilities(contracts, libraryCompatibilityReport, { generatedBy }), cwd);
|
|
680
|
+
const markdown = markdownForContracts(handlerExecutionModes, libraryContractSchema, libraryCapabilities, libraryCompatibilityReport);
|
|
681
|
+
const diagnostics = (libraryCompatibilityReport.diagnostics || []).map((diag) => normalizeDiagnostic(diag));
|
|
682
|
+
|
|
683
|
+
const artifact = normalizeArtifact({
|
|
684
|
+
version: HANDLER_LIBRARY_CONTRACTS_VERSION,
|
|
685
|
+
generatedBy,
|
|
686
|
+
phase: PHASE,
|
|
687
|
+
status: diagnostics.length === 0 ? 'ok' : 'error',
|
|
688
|
+
scope: {
|
|
689
|
+
semanticsChanged: false,
|
|
690
|
+
runtimeBehaviorChanged: false,
|
|
691
|
+
compiledHandlersImplemented: false,
|
|
692
|
+
sidecarsCompiled: false,
|
|
693
|
+
wasiMapped: false,
|
|
694
|
+
effectRuntimeImplemented: false
|
|
695
|
+
},
|
|
696
|
+
decisions: {
|
|
697
|
+
handlerExecutionModes: ALLOWED_HANDLER_EXECUTION_MODES,
|
|
698
|
+
libraryCompatibilityModes: ALLOWED_LIBRARY_MODES,
|
|
699
|
+
libraryContractsRequiredForWasm: true,
|
|
700
|
+
sidecarsExplicit: true,
|
|
701
|
+
effectsExplicit: true,
|
|
702
|
+
wasiIsProviderNotAppApi: true,
|
|
703
|
+
asyncIsEffectsNotLanguageAsync: true,
|
|
704
|
+
automaticFallback: false
|
|
705
|
+
},
|
|
706
|
+
summary: {
|
|
707
|
+
handlerModes: handlerExecutionModes.summary.modes,
|
|
708
|
+
libraries: libraryCapabilities.summary.libraries,
|
|
709
|
+
wasmSidecars: libraryCapabilities.summary.wasmSidecars,
|
|
710
|
+
lowerings: libraryCapabilities.summary.lowerings,
|
|
711
|
+
hostCapabilities: libraryCapabilities.summary.requiredHostCapabilities,
|
|
712
|
+
diagnostics: diagnostics.length,
|
|
713
|
+
readyFor11E: true,
|
|
714
|
+
readyFor11F: false,
|
|
715
|
+
readyForCompiledHandlers: false,
|
|
716
|
+
readyForFastly: false
|
|
717
|
+
}
|
|
718
|
+
}, cwd);
|
|
719
|
+
|
|
720
|
+
return {
|
|
721
|
+
artifact,
|
|
722
|
+
handlerExecutionModes,
|
|
723
|
+
libraryContractSchema,
|
|
724
|
+
libraryCapabilities,
|
|
725
|
+
libraryCompatibilityReport,
|
|
726
|
+
diagnostics,
|
|
727
|
+
files: [
|
|
728
|
+
{ file: 'generated/host/handler-library-contracts.md', text: markdown }
|
|
729
|
+
]
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
module.exports = {
|
|
734
|
+
HANDLER_LIBRARY_CONTRACTS_VERSION,
|
|
735
|
+
HANDLER_EXECUTION_MODES_VERSION,
|
|
736
|
+
LIBRARY_CONTRACT_SCHEMA_VERSION,
|
|
737
|
+
LIBRARY_CONTRACT_VERSION,
|
|
738
|
+
LIBRARY_CAPABILITIES_VERSION,
|
|
739
|
+
LIBRARY_COMPATIBILITY_REPORT_VERSION,
|
|
740
|
+
ALLOWED_HANDLER_EXECUTION_MODES,
|
|
741
|
+
ALLOWED_LIBRARY_MODES,
|
|
742
|
+
ALLOWED_HOST_CAPABILITIES,
|
|
743
|
+
defaultLibraryContracts,
|
|
744
|
+
packageDirsFromNodeModules,
|
|
745
|
+
discoverLowerableLibraryManifests,
|
|
746
|
+
resolveLowerableCompilerBuilder,
|
|
747
|
+
loadLowerableCompilerBuilder,
|
|
748
|
+
invokeLowerableCompilerBuilder,
|
|
749
|
+
resolveDefaultLibraryContracts,
|
|
750
|
+
validateLibraryContract,
|
|
751
|
+
buildHandlerLibraryContracts
|
|
752
|
+
};
|