@pulse-compute/grip 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.
@@ -0,0 +1,1063 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ const phaseName = 'grip-lowering-plan';
7
+
8
+ function loadContractsDiagnostics() {
9
+ try {
10
+ return require('@pulse-compute/wasm-contracts/diagnostics');
11
+ } catch (error) {
12
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
13
+ return require('../../wasm/packages/contracts/src/diagnostics.js');
14
+ }
15
+ throw error;
16
+ }
17
+ }
18
+
19
+ function loadGripContracts() {
20
+ try {
21
+ return require('@pulse-compute/wasm-contracts/grip/contracts');
22
+ } catch (error) {
23
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
24
+ return require('../../wasm/packages/contracts/src/grip/contracts.js');
25
+ }
26
+ throw error;
27
+ }
28
+ }
29
+
30
+ function loadCanonicalRuntimeContracts() {
31
+ try {
32
+ return require('@pulse-compute/wasm-contracts/handler/canonical-runtime');
33
+ } catch (error) {
34
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) {
35
+ return require('../../wasm/packages/contracts/src/handler/canonical-runtime.js');
36
+ }
37
+ throw error;
38
+ }
39
+ }
40
+
41
+ const {
42
+ PACKAGE_VERSION,
43
+ normalizeArtifact,
44
+ normalizeDiagnostic,
45
+ sourceLoc,
46
+ stableFileName
47
+ } = loadContractsDiagnostics();
48
+ const gripContracts = loadGripContracts();
49
+ const canonicalRuntimeContracts = loadCanonicalRuntimeContracts();
50
+
51
+ function clone(value) {
52
+ if (Array.isArray(value)) return value.map(clone);
53
+ if (value && typeof value === 'object') {
54
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clone(entry)]));
55
+ }
56
+ return value;
57
+ }
58
+
59
+ function loadTypeScript(inputs = {}) {
60
+ if (inputs.typescript) return inputs.typescript;
61
+ if (inputs.ts) return inputs.ts;
62
+ try {
63
+ return require('typescript');
64
+ } catch (error) {
65
+ const diagnostic = normalizeDiagnostic({
66
+ phase: phaseName,
67
+ severity: 'error',
68
+ code: 'PULSEWASM_GRIP_TYPESCRIPT_REQUIRED',
69
+ message: 'GRIP lowering plan extraction requires the TypeScript parser.',
70
+ hint: 'Call buildGripLoweringPlan from the compiler orchestrator, or pass { typescript } explicitly.',
71
+ loc: { file: '<grip-lowering-plan>' }
72
+ });
73
+ const thrown = new Error(diagnostic.message);
74
+ thrown.diagnostics = [diagnostic];
75
+ throw thrown;
76
+ }
77
+ }
78
+
79
+ function unwrapExpression(ts, node) {
80
+ let current = node;
81
+ while (
82
+ current &&
83
+ (ts.isParenthesizedExpression(current) ||
84
+ ts.isAsExpression(current) ||
85
+ ts.isNonNullExpression(current) ||
86
+ ts.isTypeAssertionExpression(current) ||
87
+ (typeof ts.isSatisfiesExpression === 'function' && ts.isSatisfiesExpression(current)))
88
+ ) {
89
+ current = current.expression;
90
+ }
91
+ return current;
92
+ }
93
+
94
+ function isStringLiteralLike(ts, node) {
95
+ return Boolean(node && (ts.isStringLiteral(node) || node.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral));
96
+ }
97
+
98
+ function literalText(ts, node) {
99
+ const unwrapped = unwrapExpression(ts, node);
100
+ return isStringLiteralLike(ts, unwrapped) ? unwrapped.text : undefined;
101
+ }
102
+
103
+ function numericLiteralValue(ts, node) {
104
+ const unwrapped = unwrapExpression(ts, node);
105
+ if (!unwrapped) return undefined;
106
+ return ts.isNumericLiteral(unwrapped) ? Number(unwrapped.text) : undefined;
107
+ }
108
+
109
+ function booleanLiteralValue(ts, node) {
110
+ const unwrapped = unwrapExpression(ts, node);
111
+ if (!unwrapped) return undefined;
112
+ if (unwrapped.kind === ts.SyntaxKind.TrueKeyword) return true;
113
+ if (unwrapped.kind === ts.SyntaxKind.FalseKeyword) return false;
114
+ return undefined;
115
+ }
116
+
117
+ function locFor(sourceFile, node) {
118
+ return node ? sourceLoc(sourceFile, node) : { file: sourceFile ? sourceFile.fileName : '<grip-lowering-plan>' };
119
+ }
120
+
121
+ function rangeFor(sourceFile, node) {
122
+ return Object.freeze({
123
+ start: node && typeof node.getStart === 'function' ? node.getStart(sourceFile) : 0,
124
+ end: node && typeof node.getEnd === 'function' ? node.getEnd() : 0
125
+ });
126
+ }
127
+
128
+ function placementForCall(ts, call) {
129
+ let current = call;
130
+ let parent = current.parent;
131
+ if (parent && ts.isAwaitExpression(parent)) {
132
+ current = parent;
133
+ parent = current.parent;
134
+ }
135
+ if (parent && ts.isVoidExpression(parent)) {
136
+ current = parent;
137
+ parent = current.parent;
138
+ }
139
+ if (parent && ts.isExpressionStatement(parent)) return 'statement';
140
+ if (parent && ts.isReturnStatement(parent)) return 'return';
141
+ if (parent && ts.isVariableDeclaration(parent) && parent.initializer === current) return 'variable';
142
+ if (
143
+ parent
144
+ && ts.isPropertyAssignment(parent)
145
+ && parent.initializer === current
146
+ && parent.parent
147
+ && ts.isObjectLiteralExpression(parent.parent)
148
+ && parent.parent.parent
149
+ && ts.isCallExpression(parent.parent.parent)
150
+ ) return 'statement';
151
+ return 'unsupported';
152
+ }
153
+
154
+ function canonicalEffect(ts, sourceFile, call, input) {
155
+ return Object.freeze({
156
+ version: canonicalRuntimeContracts.CANONICAL_PACKAGE_EFFECT_VERSION,
157
+ contractId: gripContracts.GRIP_CONTRACT_ID,
158
+ package: '@pulse-compute/grip',
159
+ import: '@pulse-compute/grip',
160
+ kind: `grip.${input.operation}`,
161
+ providerKind: 'grip',
162
+ operation: input.operation,
163
+ capability: `grip.${input.operation}`,
164
+ result: input.result,
165
+ placement: placementForCall(ts, call),
166
+ range: rangeFor(sourceFile, call),
167
+ resource: input.resource,
168
+ payload: Object.freeze({ ...(input.payload || {}) }),
169
+ loc: locFor(sourceFile, call)
170
+ });
171
+ }
172
+
173
+ function makeDiagnostic(sourceFile, node, code, message, hint, severity = 'error', details) {
174
+ return normalizeDiagnostic({
175
+ phase: phaseName,
176
+ severity,
177
+ code,
178
+ message,
179
+ hint,
180
+ details,
181
+ loc: locFor(sourceFile, node)
182
+ });
183
+ }
184
+
185
+ function diagnosticHasError(diagnostics) {
186
+ return (diagnostics || []).some((entry) => (entry.severity || 'error') === 'error');
187
+ }
188
+
189
+ function propertyKey(ts, name) {
190
+ if (!name) return undefined;
191
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
192
+ return undefined;
193
+ }
194
+
195
+ function firstPropertyInitializer(ts, objectExpression, names) {
196
+ if (!objectExpression || !ts.isObjectLiteralExpression(objectExpression)) return undefined;
197
+ const wanted = new Set(names);
198
+ for (const property of objectExpression.properties || []) {
199
+ if (!ts.isPropertyAssignment(property)) continue;
200
+ const key = propertyKey(ts, property.name);
201
+ if (wanted.has(key)) return property.initializer;
202
+ }
203
+ return undefined;
204
+ }
205
+
206
+ function stringArrayPropertyValue(ts, sourceFile, objectExpression, names, diagnostics, diagnosticCode, diagnosticMessage) {
207
+ const initializer = firstPropertyInitializer(ts, objectExpression, names);
208
+ if (!initializer) return undefined;
209
+ const unwrapped = unwrapExpression(ts, initializer);
210
+ if (!unwrapped || !ts.isArrayLiteralExpression(unwrapped)) {
211
+ diagnostics.push(makeDiagnostic(
212
+ sourceFile,
213
+ initializer,
214
+ diagnosticCode,
215
+ diagnosticMessage,
216
+ 'Use a static array of string literal channels in the lowerable GRIP facade.',
217
+ 'error',
218
+ { property: names[0] }
219
+ ));
220
+ return undefined;
221
+ }
222
+ const out = [];
223
+ for (const element of unwrapped.elements || []) {
224
+ const value = literalText(ts, element);
225
+ if (value === undefined) {
226
+ diagnostics.push(makeDiagnostic(
227
+ sourceFile,
228
+ element,
229
+ diagnosticCode,
230
+ diagnosticMessage,
231
+ 'Use only literal channel names in the lowerable GRIP facade.',
232
+ 'error',
233
+ { property: names[0] }
234
+ ));
235
+ } else {
236
+ out.push(value);
237
+ }
238
+ }
239
+ return out;
240
+ }
241
+
242
+ function stringPropertyValue(ts, objectExpression, names) {
243
+ const initializer = firstPropertyInitializer(ts, objectExpression, names);
244
+ if (!initializer) return undefined;
245
+ return literalText(ts, initializer);
246
+ }
247
+
248
+ function numberPropertyValue(ts, objectExpression, names) {
249
+ const initializer = firstPropertyInitializer(ts, objectExpression, names);
250
+ if (!initializer) return undefined;
251
+ return numericLiteralValue(ts, initializer);
252
+ }
253
+
254
+ function booleanPropertyValue(ts, objectExpression, names) {
255
+ const initializer = firstPropertyInitializer(ts, objectExpression, names);
256
+ if (!initializer) return undefined;
257
+ return booleanLiteralValue(ts, initializer);
258
+ }
259
+
260
+ function collectFacadeBindings(ts, sourceFile, manifest) {
261
+ const bindings = {
262
+ imports: [],
263
+ namespaceLocals: new Map(),
264
+ isWebSocketLocals: new Map(),
265
+ subscribeLocals: new Map(),
266
+ handoffLocals: new Map(),
267
+ broadcastLocals: new Map(),
268
+ holdLocals: new Map(),
269
+ channelLocals: new Map(),
270
+ publishLocals: new Map()
271
+ };
272
+ const lowerableSubpath = manifest.lowerableSubpath;
273
+ const compatibilitySubpaths = new Set(
274
+ manifest.publicApi && Array.isArray(manifest.publicApi.compatibilitySubpaths)
275
+ ? manifest.publicApi.compatibilitySubpaths.map(String)
276
+ : []
277
+ );
278
+ const facadeNamespace = manifest.facade && manifest.facade.namespace ? manifest.facade.namespace : 'grip';
279
+
280
+ function rememberImport(record) {
281
+ bindings.imports.push(record);
282
+ }
283
+
284
+ for (const statement of sourceFile.statements || []) {
285
+ if (!ts.isImportDeclaration(statement)) continue;
286
+ const moduleName = statement.moduleSpecifier && literalText(ts, statement.moduleSpecifier);
287
+ if (moduleName !== lowerableSubpath && !compatibilitySubpaths.has(moduleName)) continue;
288
+ const canonicalRoot = moduleName === lowerableSubpath;
289
+ const clause = statement.importClause;
290
+ if (!clause) continue;
291
+ if (clause.name) {
292
+ bindings.namespaceLocals.set(clause.name.text, { imported: 'default', module: moduleName, canonicalRoot, loc: locFor(sourceFile, clause.name) });
293
+ rememberImport({ imported: 'default', local: clause.name.text, module: moduleName, canonicalRoot, loc: locFor(sourceFile, clause.name) });
294
+ }
295
+ const namedBindings = clause.namedBindings;
296
+ if (!namedBindings) continue;
297
+ if (ts.isNamespaceImport(namedBindings)) {
298
+ bindings.namespaceLocals.set(namedBindings.name.text, { imported: '*', module: moduleName, canonicalRoot, loc: locFor(sourceFile, namedBindings.name) });
299
+ rememberImport({ imported: '*', local: namedBindings.name.text, module: moduleName, canonicalRoot, loc: locFor(sourceFile, namedBindings.name) });
300
+ } else if (ts.isNamedImports(namedBindings)) {
301
+ for (const specifier of namedBindings.elements || []) {
302
+ const imported = specifier.propertyName ? specifier.propertyName.text : specifier.name.text;
303
+ const local = specifier.name.text;
304
+ if (imported === facadeNamespace) {
305
+ bindings.namespaceLocals.set(local, { imported, module: moduleName, canonicalRoot, loc: locFor(sourceFile, specifier.name) });
306
+ rememberImport({ imported, local, module: moduleName, canonicalRoot, loc: locFor(sourceFile, specifier.name) });
307
+ } else if (canonicalRoot && imported === 'isWebSocket') {
308
+ bindings.isWebSocketLocals.set(local, { imported, module: moduleName, canonicalRoot, loc: locFor(sourceFile, specifier.name) });
309
+ rememberImport({ imported, local, module: moduleName, canonicalRoot, loc: locFor(sourceFile, specifier.name) });
310
+ } else if (canonicalRoot && imported === 'subscribe') {
311
+ bindings.subscribeLocals.set(local, { imported, module: moduleName, canonicalRoot, loc: locFor(sourceFile, specifier.name) });
312
+ rememberImport({ imported, local, module: moduleName, canonicalRoot, loc: locFor(sourceFile, specifier.name) });
313
+ } else if (canonicalRoot && imported === 'handoff') {
314
+ bindings.handoffLocals.set(local, { imported, module: moduleName, canonicalRoot, loc: locFor(sourceFile, specifier.name) });
315
+ rememberImport({ imported, local, module: moduleName, canonicalRoot, loc: locFor(sourceFile, specifier.name) });
316
+ } else if (canonicalRoot && imported === 'broadcast') {
317
+ bindings.broadcastLocals.set(local, { imported, module: moduleName, canonicalRoot, loc: locFor(sourceFile, specifier.name) });
318
+ rememberImport({ imported, local, module: moduleName, canonicalRoot, loc: locFor(sourceFile, specifier.name) });
319
+ } else if (imported === 'hold') {
320
+ bindings.holdLocals.set(local, { imported, loc: locFor(sourceFile, specifier.name) });
321
+ rememberImport({ imported, local, loc: locFor(sourceFile, specifier.name) });
322
+ } else if (imported === 'channel') {
323
+ bindings.channelLocals.set(local, { imported, loc: locFor(sourceFile, specifier.name) });
324
+ rememberImport({ imported, local, loc: locFor(sourceFile, specifier.name) });
325
+ } else if (imported === 'publish') {
326
+ bindings.publishLocals.set(local, { imported, local, loc: locFor(sourceFile, specifier.name) });
327
+ rememberImport({ imported, local, loc: locFor(sourceFile, specifier.name) });
328
+ }
329
+ }
330
+ }
331
+ }
332
+ return bindings;
333
+ }
334
+
335
+ function resolveGripCall(ts, call, bindings) {
336
+ const expression = unwrapExpression(ts, call.expression);
337
+ if (!expression) return undefined;
338
+ if (ts.isPropertyAccessExpression(expression)) {
339
+ const object = unwrapExpression(ts, expression.expression);
340
+ const method = expression.name.text;
341
+ const namespace = object && ts.isIdentifier(object) ? bindings.namespaceLocals.get(object.text) : undefined;
342
+ const methods = namespace && namespace.canonicalRoot
343
+ ? ['broadcast', 'handoff', 'isWebSocket', 'subscribe']
344
+ : ['hold', 'channel', 'publish'];
345
+ if (namespace && methods.includes(method)) {
346
+ return { symbol: `grip.${method}`, method, local: object.text, canonicalRoot: namespace.canonicalRoot };
347
+ }
348
+ }
349
+ if (ts.isIdentifier(expression)) {
350
+ if (bindings.isWebSocketLocals.has(expression.text)) return { symbol: 'grip.isWebSocket', method: 'isWebSocket', local: expression.text, canonicalRoot: true };
351
+ if (bindings.subscribeLocals.has(expression.text)) return { symbol: 'grip.subscribe', method: 'subscribe', local: expression.text, canonicalRoot: true };
352
+ if (bindings.handoffLocals.has(expression.text)) return { symbol: 'grip.handoff', method: 'handoff', local: expression.text, canonicalRoot: true };
353
+ if (bindings.broadcastLocals.has(expression.text)) return { symbol: 'grip.broadcast', method: 'broadcast', local: expression.text, canonicalRoot: true };
354
+ if (bindings.holdLocals.has(expression.text)) return { symbol: 'grip.hold', method: 'hold', local: expression.text };
355
+ if (bindings.channelLocals.has(expression.text)) return { symbol: 'grip.channel', method: 'channel', local: expression.text };
356
+ if (bindings.publishLocals.has(expression.text)) return { symbol: 'grip.publish', method: 'publish', local: expression.text };
357
+ }
358
+ return undefined;
359
+ }
360
+
361
+ function objectArg(ts, call, index) {
362
+ const candidate = unwrapExpression(ts, (call.arguments || [])[index]);
363
+ return candidate && ts.isObjectLiteralExpression(candidate) ? candidate : undefined;
364
+ }
365
+
366
+ function holdEntry(ts, sourceFile, call, diagnostics) {
367
+ const modeArg = (call.arguments || [])[0];
368
+ const mode = literalText(ts, modeArg);
369
+ if (mode === undefined) {
370
+ diagnostics.push(makeDiagnostic(
371
+ sourceFile,
372
+ modeArg || call,
373
+ gripContracts.GRIP_DIAGNOSTIC_CODES.NON_LITERAL_HOLD_MODE,
374
+ 'grip.hold(...) requires a literal hold mode.',
375
+ 'Use grip.hold("stream") or grip.hold("response") in the lowerable GRIP facade.',
376
+ 'error'
377
+ ));
378
+ } else if (!gripContracts.GRIP_ALLOWED_HOLD_MODES.includes(mode)) {
379
+ diagnostics.push(makeDiagnostic(
380
+ sourceFile,
381
+ modeArg,
382
+ gripContracts.GRIP_DIAGNOSTIC_CODES.HOLD_MODE_UNSUPPORTED,
383
+ `Unsupported GRIP hold mode ${JSON.stringify(mode)}.`,
384
+ `Use one of: ${gripContracts.GRIP_ALLOWED_HOLD_MODES.join(', ')}.`,
385
+ 'error',
386
+ { mode }
387
+ ));
388
+ }
389
+ const options = objectArg(ts, call, 1);
390
+ const channels = stringArrayPropertyValue(
391
+ ts,
392
+ sourceFile,
393
+ options,
394
+ ['channels'],
395
+ diagnostics,
396
+ gripContracts.GRIP_DIAGNOSTIC_CODES.DYNAMIC_CHANNEL_LIST_UNSUPPORTED,
397
+ 'grip.hold(...) channels must be a static array of literal channel names.'
398
+ );
399
+ const timeoutMs = numberPropertyValue(ts, options, ['timeoutMs', 'timeout']);
400
+ const headersConfigured = Boolean(options && firstPropertyInitializer(ts, options, ['headers']));
401
+ const entry = {
402
+ kind: 'grip-hold',
403
+ symbol: 'grip.hold',
404
+ mode,
405
+ channels: channels || [],
406
+ timeoutMs,
407
+ headersConfigured,
408
+ asSymbol: 'pulse_grip_hold',
409
+ hostCapabilities: ['headers'],
410
+ loc: locFor(sourceFile, call),
411
+ range: rangeFor(sourceFile, call),
412
+ placement: placementForCall(ts, call)
413
+ };
414
+ entry.canonicalEffect = canonicalEffect(ts, sourceFile, call, {
415
+ import: '@pulse-compute/grip/pulsewasm',
416
+ operation: 'hold',
417
+ result: 'opaque-response',
418
+ resource: Object.freeze({ kind: 'literal', value: mode || 'stream' }),
419
+ payload: {
420
+ mode,
421
+ channels: channels || [],
422
+ timeoutMs,
423
+ headersConfigured
424
+ }
425
+ });
426
+ return entry;
427
+ }
428
+
429
+ function channelEntry(ts, sourceFile, call, diagnostics) {
430
+ const channelArg = (call.arguments || [])[0];
431
+ const channelName = literalText(ts, channelArg);
432
+ if (channelName === undefined) {
433
+ diagnostics.push(makeDiagnostic(
434
+ sourceFile,
435
+ channelArg || call,
436
+ gripContracts.GRIP_DIAGNOSTIC_CODES.NON_LITERAL_CHANNEL,
437
+ 'grip.channel(...) requires a literal channel name.',
438
+ 'Use a static channel string in the lowerable GRIP facade. Dynamic channel expressions remain out of scope for the Beta.',
439
+ 'error'
440
+ ));
441
+ }
442
+ const options = objectArg(ts, call, 1);
443
+ const prefix = stringPropertyValue(ts, options, ['prefix']);
444
+ const fanout = booleanPropertyValue(ts, options, ['fanout']);
445
+ const entry = {
446
+ kind: 'grip-channel',
447
+ symbol: 'grip.channel',
448
+ channel: channelName,
449
+ prefix,
450
+ fanout,
451
+ asSymbol: 'pulse_grip_channel',
452
+ hostCapabilities: ['headers', 'channel', 'broadcaster'],
453
+ loc: locFor(sourceFile, call),
454
+ range: rangeFor(sourceFile, call),
455
+ placement: placementForCall(ts, call)
456
+ };
457
+ entry.canonicalEffect = canonicalEffect(ts, sourceFile, call, {
458
+ import: '@pulse-compute/grip/pulsewasm',
459
+ operation: 'channel',
460
+ result: 'ack',
461
+ resource: Object.freeze({ kind: 'literal', value: channelName }),
462
+ payload: { channel: channelName, prefix, fanout }
463
+ });
464
+ return entry;
465
+ }
466
+
467
+ function publishEntry(ts, sourceFile, call, diagnostics) {
468
+ const channelArg = (call.arguments || [])[0];
469
+ const messageArg = (call.arguments || [])[1];
470
+ const channelName = literalText(ts, channelArg);
471
+ const message = literalText(ts, messageArg);
472
+ if (channelName === undefined) {
473
+ diagnostics.push(makeDiagnostic(
474
+ sourceFile,
475
+ channelArg || call,
476
+ gripContracts.GRIP_DIAGNOSTIC_CODES.NON_LITERAL_CHANNEL,
477
+ 'grip.publish(...) requires a literal channel name.',
478
+ 'Use a static channel string in the lowerable GRIP facade. Dynamic channel expressions remain out of scope for the Beta.',
479
+ 'error'
480
+ ));
481
+ }
482
+ if (message === undefined) {
483
+ diagnostics.push(makeDiagnostic(
484
+ sourceFile,
485
+ messageArg || call,
486
+ gripContracts.GRIP_DIAGNOSTIC_CODES.NON_LITERAL_MESSAGE,
487
+ 'grip.publish(...) requires a literal message payload in the Beta.',
488
+ 'Use a static string message for the Beta lowerable contract. Schema-backed or dynamic publish payloads are future work.',
489
+ 'error'
490
+ ));
491
+ }
492
+ const options = objectArg(ts, call, 2);
493
+ const entry = {
494
+ kind: 'grip-publish',
495
+ symbol: 'grip.publish',
496
+ channel: channelName,
497
+ message,
498
+ event: stringPropertyValue(ts, options, ['event', 'type']),
499
+ id: stringPropertyValue(ts, options, ['id']),
500
+ asSymbol: 'pulse_grip_publish',
501
+ hostCapabilities: ['broadcaster', 'clock'],
502
+ loc: locFor(sourceFile, call),
503
+ range: rangeFor(sourceFile, call),
504
+ placement: placementForCall(ts, call)
505
+ };
506
+ entry.canonicalEffect = canonicalEffect(ts, sourceFile, call, {
507
+ import: '@pulse-compute/grip/pulsewasm',
508
+ operation: 'publish',
509
+ result: 'structured-response',
510
+ resource: Object.freeze({ kind: 'literal', value: channelName }),
511
+ payload: {
512
+ channel: channelName,
513
+ message,
514
+ event: entry.event,
515
+ id: entry.id
516
+ }
517
+ });
518
+ return entry;
519
+ }
520
+
521
+ function enclosingContextName(ts, node) {
522
+ let current = node && node.parent;
523
+ while (current) {
524
+ if (
525
+ (ts.isFunctionDeclaration(current)
526
+ || ts.isFunctionExpression(current)
527
+ || ts.isArrowFunction(current)
528
+ || ts.isMethodDeclaration(current))
529
+ && current.parameters
530
+ && current.parameters[0]
531
+ && ts.isIdentifier(current.parameters[0].name)
532
+ ) return current.parameters[0].name.text;
533
+ current = current.parent;
534
+ }
535
+ return 'ctx';
536
+ }
537
+
538
+ function staticJsonValue(ts, sourceFile, node, diagnostics, diagnosticCode, message) {
539
+ const current = unwrapExpression(ts, node);
540
+ if (!current) return undefined;
541
+ if (isStringLiteralLike(ts, current)) return current.text;
542
+ if (ts.isNumericLiteral(current)) return Number(current.text);
543
+ if (current.kind === ts.SyntaxKind.TrueKeyword) return true;
544
+ if (current.kind === ts.SyntaxKind.FalseKeyword) return false;
545
+ if (current.kind === ts.SyntaxKind.NullKeyword) return null;
546
+ if (
547
+ ts.isPrefixUnaryExpression(current)
548
+ && current.operator === ts.SyntaxKind.MinusToken
549
+ && ts.isNumericLiteral(current.operand)
550
+ ) return -Number(current.operand.text);
551
+ if (ts.isArrayLiteralExpression(current)) {
552
+ const output = [];
553
+ for (const element of current.elements || []) {
554
+ if (ts.isSpreadElement(element) || ts.isOmittedExpression(element)) {
555
+ diagnostics.push(makeDiagnostic(
556
+ sourceFile,
557
+ element,
558
+ diagnosticCode,
559
+ message,
560
+ 'Use JSON-literal arrays without spreads or omitted elements.',
561
+ 'error'
562
+ ));
563
+ continue;
564
+ }
565
+ output.push(staticJsonValue(ts, sourceFile, element, diagnostics, diagnosticCode, message));
566
+ }
567
+ return output;
568
+ }
569
+ if (ts.isObjectLiteralExpression(current)) {
570
+ const output = {};
571
+ const seen = new Set();
572
+ for (const property of current.properties || []) {
573
+ if (!ts.isPropertyAssignment(property)) {
574
+ diagnostics.push(makeDiagnostic(
575
+ sourceFile,
576
+ property,
577
+ diagnosticCode,
578
+ message,
579
+ 'Use an ordinary JSON-literal object without spreads, shorthand properties, methods, getters, or setters.',
580
+ 'error'
581
+ ));
582
+ continue;
583
+ }
584
+ const key = propertyKey(ts, property.name);
585
+ if (key === undefined || seen.has(key) || key === '__proto__') {
586
+ diagnostics.push(makeDiagnostic(
587
+ sourceFile,
588
+ property.name,
589
+ diagnosticCode,
590
+ message,
591
+ 'Use unique static object keys; computed, duplicate, and __proto__ keys are not lowerable.',
592
+ 'error'
593
+ ));
594
+ continue;
595
+ }
596
+ seen.add(key);
597
+ output[key] = staticJsonValue(ts, sourceFile, property.initializer, diagnostics, diagnosticCode, message);
598
+ }
599
+ return output;
600
+ }
601
+ diagnostics.push(makeDiagnostic(
602
+ sourceFile,
603
+ current,
604
+ diagnosticCode,
605
+ message,
606
+ 'Use only JSON-literal values in the bounded Native GRIP surface.',
607
+ 'error',
608
+ { expression: current.getText(sourceFile) }
609
+ ));
610
+ return undefined;
611
+ }
612
+
613
+ function staticObjectArgument(ts, sourceFile, call, index, diagnostics, diagnosticCode, message) {
614
+ const argument = call.arguments && call.arguments[index];
615
+ const current = unwrapExpression(ts, argument);
616
+ if (!current || !ts.isObjectLiteralExpression(current)) {
617
+ diagnostics.push(makeDiagnostic(
618
+ sourceFile,
619
+ argument || call,
620
+ diagnosticCode,
621
+ message,
622
+ 'Use an inline static object literal in the bounded Native GRIP surface.',
623
+ 'error'
624
+ ));
625
+ return {};
626
+ }
627
+ return staticJsonValue(ts, sourceFile, current, diagnostics, diagnosticCode, message) || {};
628
+ }
629
+
630
+ function normalizeStaticChannels(sourceFile, node, options, diagnostics) {
631
+ const input = options.channels !== undefined
632
+ ? options.channels
633
+ : (options.channel === undefined ? [] : [options.channel]);
634
+ if (!Array.isArray(input) || input.length === 0) {
635
+ diagnostics.push(makeDiagnostic(
636
+ sourceFile,
637
+ node,
638
+ gripContracts.GRIP_DIAGNOSTIC_CODES.CHANNEL_INVALID,
639
+ 'GRIP framing requires at least one static channel.',
640
+ 'Use channel: "name" or channels: ["one", "two"].',
641
+ 'error'
642
+ ));
643
+ return [];
644
+ }
645
+ return input.map((value) => {
646
+ const channel = typeof value === 'string' ? value.trim() : '';
647
+ if (!channel || /[,\r\n]/.test(channel)) {
648
+ diagnostics.push(makeDiagnostic(
649
+ sourceFile,
650
+ node,
651
+ gripContracts.GRIP_DIAGNOSTIC_CODES.CHANNEL_INVALID,
652
+ 'GRIP channel names must be non-empty static strings without commas or line breaks.',
653
+ 'Use one literal channel per ordered channel entry.',
654
+ 'error',
655
+ { channel: typeof value === 'string' ? value : undefined }
656
+ ));
657
+ }
658
+ return channel;
659
+ });
660
+ }
661
+
662
+ function validateFramingOptions(sourceFile, node, options, diagnostics, kind) {
663
+ normalizeStaticChannels(sourceFile, node, options, diagnostics);
664
+ if (options.mode !== undefined && !gripContracts.GRIP_ALLOWED_HOLD_MODES.includes(options.mode)) {
665
+ diagnostics.push(makeDiagnostic(
666
+ sourceFile,
667
+ node,
668
+ gripContracts.GRIP_DIAGNOSTIC_CODES.MODE_UNSUPPORTED,
669
+ `Unsupported GRIP hold mode ${JSON.stringify(options.mode)}.`,
670
+ 'Use "stream" or "response".',
671
+ 'error',
672
+ { mode: options.mode }
673
+ ));
674
+ }
675
+ if (
676
+ options.timeoutMs !== undefined
677
+ && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 0)
678
+ ) {
679
+ diagnostics.push(makeDiagnostic(
680
+ sourceFile,
681
+ node,
682
+ gripContracts.GRIP_DIAGNOSTIC_CODES.TIMEOUT_INVALID,
683
+ 'GRIP timeoutMs must be a non-negative safe integer.',
684
+ 'Use zero or a positive integer no larger than Number.MAX_SAFE_INTEGER.',
685
+ 'error',
686
+ { timeoutMs: options.timeoutMs }
687
+ ));
688
+ }
689
+ if (
690
+ kind === 'handoff'
691
+ && options.status !== undefined
692
+ && (!Number.isSafeInteger(options.status) || options.status < 200 || options.status > 599)
693
+ ) {
694
+ diagnostics.push(makeDiagnostic(
695
+ sourceFile,
696
+ node,
697
+ gripContracts.GRIP_DIAGNOSTIC_CODES.STATIC_OPTIONS_REQUIRED,
698
+ 'GRIP handoff status must be a static integer from 200 through 599.',
699
+ 'Use a valid Web Response status.',
700
+ 'error',
701
+ { status: options.status }
702
+ ));
703
+ }
704
+ }
705
+
706
+ function packageIntrinsic(sourceFile, call, operation) {
707
+ const descriptor = gripContracts.GRIP_PACKAGE_INTRINSICS[operation];
708
+ return Object.freeze({
709
+ version: gripContracts.GRIP_PACKAGE_INTRINSIC_VERSION,
710
+ contractId: gripContracts.GRIP_CONTRACT_ID,
711
+ package: '@pulse-compute/grip',
712
+ import: '@pulse-compute/grip',
713
+ kind: `grip.${operation}`,
714
+ operation,
715
+ intrinsic: descriptor.name,
716
+ compilerName: descriptor.compilerName,
717
+ valueKind: descriptor.valueKind,
718
+ argumentIndexes: descriptor.argumentIndexes,
719
+ range: rangeFor(sourceFile, call),
720
+ loc: locFor(sourceFile, call)
721
+ });
722
+ }
723
+
724
+ function isWebSocketEntry(ts, sourceFile, call, diagnostics) {
725
+ const argument = unwrapExpression(ts, call.arguments && call.arguments[0]);
726
+ const contextName = enclosingContextName(ts, call);
727
+ const valid = call.arguments.length === 1
728
+ && argument
729
+ && ts.isPropertyAccessExpression(argument)
730
+ && argument.name.text === 'req'
731
+ && ts.isIdentifier(unwrapExpression(ts, argument.expression))
732
+ && unwrapExpression(ts, argument.expression).text === contextName;
733
+ if (!valid) {
734
+ diagnostics.push(makeDiagnostic(
735
+ sourceFile,
736
+ argument || call,
737
+ gripContracts.GRIP_DIAGNOSTIC_CODES.REQUEST_EXPRESSION_UNSUPPORTED,
738
+ 'grip.isWebSocket(...) requires the current canonical handler request.',
739
+ `Call grip.isWebSocket(${contextName}.req) directly.`,
740
+ 'error'
741
+ ));
742
+ }
743
+ return Object.freeze({
744
+ kind: 'grip-is-websocket',
745
+ symbol: 'grip.isWebSocket',
746
+ loc: locFor(sourceFile, call),
747
+ range: rangeFor(sourceFile, call),
748
+ placement: placementForCall(ts, call),
749
+ canonicalIntrinsic: packageIntrinsic(sourceFile, call, 'isWebSocket')
750
+ });
751
+ }
752
+
753
+ function subscribeEntry(ts, sourceFile, call, diagnostics) {
754
+ if (call.arguments.length !== 2) {
755
+ diagnostics.push(makeDiagnostic(
756
+ sourceFile,
757
+ call,
758
+ gripContracts.GRIP_DIAGNOSTIC_CODES.ARGUMENT_SHAPE_UNSUPPORTED,
759
+ 'grip.subscribe(...) requires a response and one static options object.',
760
+ 'Call grip.subscribe(response, { channel, mode, timeoutMs }).',
761
+ 'error'
762
+ ));
763
+ }
764
+ const options = staticObjectArgument(
765
+ ts,
766
+ sourceFile,
767
+ call,
768
+ 1,
769
+ diagnostics,
770
+ gripContracts.GRIP_DIAGNOSTIC_CODES.STATIC_OPTIONS_REQUIRED,
771
+ 'grip.subscribe options must be a static object.'
772
+ );
773
+ validateFramingOptions(sourceFile, call.arguments[1] || call, options, diagnostics, 'subscribe');
774
+ return Object.freeze({
775
+ kind: 'grip-subscribe',
776
+ symbol: 'grip.subscribe',
777
+ options: clone(options),
778
+ loc: locFor(sourceFile, call),
779
+ range: rangeFor(sourceFile, call),
780
+ placement: placementForCall(ts, call),
781
+ canonicalIntrinsic: packageIntrinsic(sourceFile, call, 'subscribe')
782
+ });
783
+ }
784
+
785
+ function handoffEntry(ts, sourceFile, call, diagnostics) {
786
+ if (call.arguments.length !== 1) {
787
+ diagnostics.push(makeDiagnostic(
788
+ sourceFile,
789
+ call,
790
+ gripContracts.GRIP_DIAGNOSTIC_CODES.ARGUMENT_SHAPE_UNSUPPORTED,
791
+ 'grip.handoff(...) requires one static options object.',
792
+ 'Call grip.handoff({ channel, status, headers, body }).',
793
+ 'error'
794
+ ));
795
+ }
796
+ const options = staticObjectArgument(
797
+ ts,
798
+ sourceFile,
799
+ call,
800
+ 0,
801
+ diagnostics,
802
+ gripContracts.GRIP_DIAGNOSTIC_CODES.STATIC_OPTIONS_REQUIRED,
803
+ 'grip.handoff options must be a static object.'
804
+ );
805
+ validateFramingOptions(sourceFile, call.arguments[0] || call, options, diagnostics, 'handoff');
806
+ return Object.freeze({
807
+ kind: 'grip-handoff',
808
+ symbol: 'grip.handoff',
809
+ options: clone(options),
810
+ loc: locFor(sourceFile, call),
811
+ range: rangeFor(sourceFile, call),
812
+ placement: placementForCall(ts, call),
813
+ canonicalIntrinsic: packageIntrinsic(sourceFile, call, 'handoff')
814
+ });
815
+ }
816
+
817
+ function broadcastEntry(ts, sourceFile, call, diagnostics) {
818
+ const contextName = enclosingContextName(ts, call);
819
+ const contextArg = unwrapExpression(ts, call.arguments && call.arguments[0]);
820
+ if (
821
+ call.arguments.length !== 2
822
+ || !contextArg
823
+ || !ts.isIdentifier(contextArg)
824
+ || contextArg.text !== contextName
825
+ ) {
826
+ diagnostics.push(makeDiagnostic(
827
+ sourceFile,
828
+ contextArg || call,
829
+ gripContracts.GRIP_DIAGNOSTIC_CODES.ARGUMENT_SHAPE_UNSUPPORTED,
830
+ 'grip.broadcast(...) requires the current handler context and one static message.',
831
+ `Call grip.broadcast(${contextName}, { channel, data, event, id }).`,
832
+ 'error'
833
+ ));
834
+ }
835
+ const message = staticObjectArgument(
836
+ ts,
837
+ sourceFile,
838
+ call,
839
+ 1,
840
+ diagnostics,
841
+ gripContracts.GRIP_DIAGNOSTIC_CODES.STATIC_MESSAGE_REQUIRED,
842
+ 'grip.broadcast message must be a static JSON-literal object.'
843
+ );
844
+ const channel = typeof message.channel === 'string' ? message.channel.trim() : '';
845
+ if (!channel || /[,\r\n]/.test(channel)) {
846
+ diagnostics.push(makeDiagnostic(
847
+ sourceFile,
848
+ call.arguments[1] || call,
849
+ gripContracts.GRIP_DIAGNOSTIC_CODES.CHANNEL_INVALID,
850
+ 'grip.broadcast channel must be a non-empty static string without commas or line breaks.',
851
+ 'Use one literal provider channel.',
852
+ 'error'
853
+ ));
854
+ }
855
+ if (!Object.prototype.hasOwnProperty.call(message, 'data')) {
856
+ diagnostics.push(makeDiagnostic(
857
+ sourceFile,
858
+ call.arguments[1] || call,
859
+ gripContracts.GRIP_DIAGNOSTIC_CODES.STATIC_MESSAGE_REQUIRED,
860
+ 'grip.broadcast message requires a static data property.',
861
+ 'Include data with a JSON-literal value.',
862
+ 'error'
863
+ ));
864
+ }
865
+ const payload = {
866
+ channel,
867
+ data: message.data,
868
+ ...(message.event === undefined ? {} : { event: String(message.event) }),
869
+ ...(message.id === undefined ? {} : { id: String(message.id) })
870
+ };
871
+ return Object.freeze({
872
+ kind: 'grip-broadcast',
873
+ symbol: 'grip.broadcast',
874
+ message: clone(payload),
875
+ loc: locFor(sourceFile, call),
876
+ range: rangeFor(sourceFile, call),
877
+ placement: placementForCall(ts, call),
878
+ canonicalEffect: canonicalEffect(ts, sourceFile, call, {
879
+ import: '@pulse-compute/grip',
880
+ operation: 'broadcast',
881
+ result: 'ack',
882
+ resource: Object.freeze({ kind: 'literal', value: channel }),
883
+ payload
884
+ })
885
+ });
886
+ }
887
+
888
+ function sourceFileFromInputs(ts, inputs) {
889
+ if (inputs.sourceFile) return inputs.sourceFile;
890
+ const sourcePath = inputs.sourcePath || inputs.entry || inputs.appPath || inputs.file;
891
+ const sourceText = inputs.sourceText !== undefined
892
+ ? String(inputs.sourceText)
893
+ : fs.readFileSync(sourcePath, 'utf8');
894
+ const fileName = sourcePath || '<grip-lowering-plan>';
895
+ return ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
896
+ }
897
+
898
+ function buildGripLoweringPlan(inputs = {}) {
899
+ const ts = loadTypeScript(inputs);
900
+ const sourceFile = sourceFileFromInputs(ts, inputs);
901
+ const manifest = inputs.gripManifest || inputs.manifest || require('./pulsewasm.manifest.cjs');
902
+ const diagnostics = [];
903
+ const bindings = collectFacadeBindings(ts, sourceFile, manifest);
904
+ const entries = [];
905
+
906
+ function visit(node) {
907
+ if (ts.isCallExpression(node)) {
908
+ const resolved = resolveGripCall(ts, node, bindings);
909
+ if (resolved) {
910
+ if (resolved.method === 'isWebSocket') entries.push(isWebSocketEntry(ts, sourceFile, node, diagnostics));
911
+ if (resolved.method === 'subscribe') entries.push(subscribeEntry(ts, sourceFile, node, diagnostics));
912
+ if (resolved.method === 'handoff') entries.push(handoffEntry(ts, sourceFile, node, diagnostics));
913
+ if (resolved.method === 'broadcast') entries.push(broadcastEntry(ts, sourceFile, node, diagnostics));
914
+ if (resolved.method === 'hold') entries.push(holdEntry(ts, sourceFile, node, diagnostics));
915
+ if (resolved.method === 'channel') entries.push(channelEntry(ts, sourceFile, node, diagnostics));
916
+ if (resolved.method === 'publish') entries.push(publishEntry(ts, sourceFile, node, diagnostics));
917
+ }
918
+ }
919
+ ts.forEachChild(node, visit);
920
+ }
921
+ visit(sourceFile);
922
+
923
+ for (const entry of entries) {
924
+ if (entry.canonicalIntrinsic) continue;
925
+ if (entry.placement !== 'statement' && entry.placement !== 'return') {
926
+ if (entry.symbol === 'grip.broadcast' && entry.placement === 'variable') continue;
927
+ diagnostics.push(makeDiagnostic(
928
+ sourceFile,
929
+ sourceFile,
930
+ gripContracts.GRIP_DIAGNOSTIC_CODES.HANDLE_ESCAPE_UNSUPPORTED,
931
+ `${entry.symbol} must be directly awaited, returned, or used as a keyed ctx.parallel member.`,
932
+ 'Keep GRIP values inside the package-owned lowering boundary; passing them through user state remains unsupported.',
933
+ 'error',
934
+ { symbol: entry.symbol, placement: entry.placement, range: entry.range }
935
+ ));
936
+ }
937
+ if (entry.symbol === 'grip.hold' && entry.placement !== 'return') {
938
+ diagnostics.push(makeDiagnostic(
939
+ sourceFile,
940
+ sourceFile,
941
+ gripContracts.GRIP_DIAGNOSTIC_CODES.HANDLE_ESCAPE_UNSUPPORTED,
942
+ 'grip.hold(...) must be returned directly so the opaque response stays host-owned.',
943
+ 'Return grip.hold(...) from the handler branch without inspecting or assigning the result.',
944
+ 'error',
945
+ { placement: entry.placement, range: entry.range }
946
+ ));
947
+ }
948
+ }
949
+
950
+ if (bindings.imports.length > 0 && entries.length === 0) {
951
+ diagnostics.push(makeDiagnostic(
952
+ sourceFile,
953
+ sourceFile,
954
+ gripContracts.GRIP_DIAGNOSTIC_CODES.HANDLE_ESCAPE_UNSUPPORTED,
955
+ 'A GRIP facade was imported but no supported package-owned calls were found.',
956
+ 'Use the bounded canonical root or the retained static compatibility facade directly in handlers.',
957
+ 'warning'
958
+ ));
959
+ }
960
+
961
+ const packageCompilerBuilder = inputs.packageCompilerBuilder || {
962
+ owner: manifest.compiler && manifest.compiler.builderOwner,
963
+ export: manifest.compiler && manifest.compiler.export,
964
+ trust: manifest.compiler && manifest.compiler.trust
965
+ };
966
+ const lowerings = manifest.modes && manifest.modes.wasm && Array.isArray(manifest.modes.wasm.lowerings)
967
+ ? manifest.modes.wasm.lowerings.map(clone)
968
+ : [];
969
+ const canonicalEffects = entries.map((entry) => entry.canonicalEffect);
970
+ const canonicalIntrinsics = entries.map((entry) => entry.canonicalIntrinsic).filter(Boolean);
971
+ const artifact = normalizeArtifact({
972
+ version: gripContracts.GRIP_LOWERING_PLAN_VERSION,
973
+ generatedBy: inputs.generatedBy || PACKAGE_VERSION,
974
+ phase: 54,
975
+ artifact: gripContracts.GRIP_LOWERING_PLAN_ARTIFACT,
976
+ contractId: gripContracts.GRIP_CONTRACT_ID,
977
+ package: manifest.npmPackage,
978
+ lowerableSubpath: manifest.lowerableSubpath,
979
+ status: diagnosticHasError(diagnostics) ? 'error' : 'ok',
980
+ source: stableFileName(sourceFile.fileName || '<grip-lowering-plan>', inputs.cwd || process.cwd()),
981
+ facade: clone(manifest.facade || {}),
982
+ manifest: {
983
+ version: manifest.version,
984
+ owner: 'package',
985
+ package: manifest.npmPackage,
986
+ lowerableSubpath: manifest.lowerableSubpath
987
+ },
988
+ packageCompilerBuilder,
989
+ builderOwner: packageCompilerBuilder.owner,
990
+ lowerings,
991
+ entries,
992
+ canonicalEffects: canonicalEffects.filter(Boolean),
993
+ canonicalIntrinsics,
994
+ diagnostics,
995
+ policy: {
996
+ packageOwnsCompilerBuilder: true,
997
+ libraryKitOwnsGenericDiscovery: true,
998
+ compilerOwnsPackageMapping: false,
999
+ providerRuntimeImplemented: true,
1000
+ providerBehaviorImplemented: true,
1001
+ productionGripRuntimeBehavior: true,
1002
+ arbitraryThirdPartyLowererExecution: false,
1003
+ betaProofOnly: false,
1004
+ dynamicChannelsSupported: false,
1005
+ dynamicPublishPayloadsSupported: false,
1006
+ canonicalRootLowering: true,
1007
+ statelessFraming: true
1008
+ },
1009
+ summary: {
1010
+ entries: entries.length,
1011
+ holdEntries: entries.filter((entry) => entry.kind === 'grip-hold').length,
1012
+ channelEntries: entries.filter((entry) => entry.kind === 'grip-channel').length,
1013
+ publishEntries: entries.filter((entry) => entry.kind === 'grip-publish').length,
1014
+ canonicalRootEntries: entries.filter((entry) => ['grip-is-websocket', 'grip-subscribe', 'grip-handoff', 'grip-broadcast'].includes(entry.kind)).length,
1015
+ intrinsicEntries: canonicalIntrinsics.length,
1016
+ broadcastEntries: entries.filter((entry) => entry.kind === 'grip-broadcast').length,
1017
+ holdCalls: entries.filter((entry) => entry.kind === 'grip-hold').length,
1018
+ channelCalls: entries.filter((entry) => entry.kind === 'grip-channel').length,
1019
+ publishCalls: entries.filter((entry) => entry.kind === 'grip-publish').length,
1020
+ imports: bindings.imports.length,
1021
+ packageOwnedBuilder: packageCompilerBuilder.owner === '@pulse-compute/grip',
1022
+ manifestOwnedByPackage: manifest.npmPackage === '@pulse-compute/grip',
1023
+ sidecarDeclared: Boolean(manifest.modes && manifest.modes.wasm && manifest.modes.wasm.sidecar),
1024
+ sidecarSymbolsDeclared: lowerings.length >= 3,
1025
+ providerRuntimeImplemented: true,
1026
+ compiledWasmRuntimeImplemented: true,
1027
+ dynamicChannelsSupported: false,
1028
+ dynamicPublishPayloadsSupported: false,
1029
+ productionCompletenessRequired: true,
1030
+ errors: diagnostics.filter((entry) => (entry.severity || 'error') === 'error').length,
1031
+ warnings: diagnostics.filter((entry) => entry.severity === 'warning').length
1032
+ }
1033
+ });
1034
+
1035
+ return {
1036
+ artifact,
1037
+ entries,
1038
+ canonicalEffects: canonicalEffects.filter(Boolean),
1039
+ canonicalIntrinsics,
1040
+ diagnostics,
1041
+ manifest,
1042
+ packageCompilerBuilder,
1043
+ hasErrors: artifact.summary.errors > 0
1044
+ };
1045
+ }
1046
+
1047
+ const gripLoweringBuilder = Object.freeze({
1048
+ owner: '@pulse-compute/grip',
1049
+ contractId: gripContracts.GRIP_CONTRACT_ID,
1050
+ artifact: gripContracts.GRIP_LOWERING_PLAN_ARTIFACT,
1051
+ build: buildGripLoweringPlan
1052
+ });
1053
+
1054
+ const GRIP_DIAGNOSTIC_CODES = Object.freeze({
1055
+ ...gripContracts.GRIP_DIAGNOSTIC_CODES,
1056
+ NON_LITERAL_MESSAGE: gripContracts.GRIP_DIAGNOSTIC_CODES.NON_LITERAL_PUBLISH_MESSAGE
1057
+ });
1058
+
1059
+ module.exports = {
1060
+ buildGripLoweringPlan,
1061
+ gripLoweringBuilder,
1062
+ GRIP_DIAGNOSTIC_CODES: gripContracts.GRIP_DIAGNOSTIC_CODES
1063
+ };