@fairfox/polly 0.1.1 → 0.1.2

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.
Files changed (39) hide show
  1. package/cli/polly.ts +9 -3
  2. package/package.json +2 -2
  3. package/vendor/analysis/src/extract/adr.ts +212 -0
  4. package/vendor/analysis/src/extract/architecture.ts +160 -0
  5. package/vendor/analysis/src/extract/contexts.ts +298 -0
  6. package/vendor/analysis/src/extract/flows.ts +309 -0
  7. package/vendor/analysis/src/extract/handlers.ts +321 -0
  8. package/vendor/analysis/src/extract/index.ts +9 -0
  9. package/vendor/analysis/src/extract/integrations.ts +329 -0
  10. package/vendor/analysis/src/extract/manifest.ts +298 -0
  11. package/vendor/analysis/src/extract/types.ts +389 -0
  12. package/vendor/analysis/src/index.ts +7 -0
  13. package/vendor/analysis/src/types/adr.ts +53 -0
  14. package/vendor/analysis/src/types/architecture.ts +245 -0
  15. package/vendor/analysis/src/types/core.ts +210 -0
  16. package/vendor/analysis/src/types/index.ts +18 -0
  17. package/vendor/verify/src/adapters/base.ts +164 -0
  18. package/vendor/verify/src/adapters/detection.ts +281 -0
  19. package/vendor/verify/src/adapters/event-bus/index.ts +480 -0
  20. package/vendor/verify/src/adapters/web-extension/index.ts +508 -0
  21. package/vendor/verify/src/adapters/websocket/index.ts +486 -0
  22. package/vendor/verify/src/cli.ts +430 -0
  23. package/vendor/verify/src/codegen/config.ts +354 -0
  24. package/vendor/verify/src/codegen/tla.ts +719 -0
  25. package/vendor/verify/src/config/parser.ts +303 -0
  26. package/vendor/verify/src/config/types.ts +113 -0
  27. package/vendor/verify/src/core/model.ts +267 -0
  28. package/vendor/verify/src/core/primitives.ts +106 -0
  29. package/vendor/verify/src/extract/handlers.ts +2 -0
  30. package/vendor/verify/src/extract/types.ts +2 -0
  31. package/vendor/verify/src/index.ts +150 -0
  32. package/vendor/verify/src/primitives/index.ts +102 -0
  33. package/vendor/verify/src/runner/docker.ts +283 -0
  34. package/vendor/verify/src/types.ts +51 -0
  35. package/vendor/visualize/src/cli.ts +365 -0
  36. package/vendor/visualize/src/codegen/structurizr.ts +770 -0
  37. package/vendor/visualize/src/index.ts +13 -0
  38. package/vendor/visualize/src/runner/export.ts +235 -0
  39. package/vendor/visualize/src/viewer/server.ts +485 -0
@@ -0,0 +1,480 @@
1
+ // ═══════════════════════════════════════════════════════════════
2
+ // Event Bus Adapter
3
+ // ═══════════════════════════════════════════════════════════════
4
+ //
5
+ // Extracts verification model from event-driven systems using EventEmitter.
6
+ // Recognizes:
7
+ // - EventEmitter instances (Node.js EventEmitter, mitt, etc.)
8
+ // - emitter.on(event, handler) pattern
9
+ // - emitter.emit(event, data) pattern
10
+ // - State mutations via state.field = value
11
+ // - Verification primitives (requires, ensures)
12
+
13
+ import { Project, type SourceFile, SyntaxKind, Node } from "ts-morph";
14
+ import type {
15
+ CoreVerificationModel,
16
+ MessageHandler,
17
+ StateAssignment,
18
+ VerificationCondition,
19
+ NodeDefinition,
20
+ MessageType,
21
+ RoutingRule,
22
+ } from "../../core/model";
23
+ import type { AdapterConfig, RoutingAdapter } from "../base";
24
+
25
+ // ─────────────────────────────────────────────────────────────────
26
+ // Configuration
27
+ // ─────────────────────────────────────────────────────────────────
28
+
29
+ export interface EventBusAdapterConfig extends AdapterConfig {
30
+ /** EventEmitter library being used (default: "events") */
31
+ emitterLibrary?: "events" | "mitt" | "eventemitter3" | "custom";
32
+
33
+ /** Pattern to match emitter instances (default: /emitter|bus|events/i) */
34
+ emitterPattern?: RegExp;
35
+
36
+ /** Maximum concurrent events to model (default: 5) */
37
+ maxInFlight?: number;
38
+
39
+ /** Maximum number of emitter instances (default: 3) */
40
+ maxEmitters?: number;
41
+ }
42
+
43
+ // ─────────────────────────────────────────────────────────────────
44
+ // Event Bus Adapter Implementation
45
+ // ─────────────────────────────────────────────────────────────────
46
+
47
+ export class EventBusAdapter implements RoutingAdapter<EventBusAdapterConfig> {
48
+ readonly name = "event-bus";
49
+ readonly config: EventBusAdapterConfig;
50
+ private project: Project;
51
+
52
+ constructor(config: EventBusAdapterConfig) {
53
+ this.config = {
54
+ emitterLibrary: "events",
55
+ emitterPattern: /emitter|bus|events/i,
56
+ maxInFlight: 5,
57
+ maxEmitters: 3,
58
+ ...config,
59
+ };
60
+
61
+ this.project = new Project({
62
+ tsConfigFilePath: config.tsConfigPath,
63
+ });
64
+ }
65
+
66
+ /**
67
+ * Extract the complete verification model from the event-driven codebase
68
+ */
69
+ extractModel(): CoreVerificationModel {
70
+ const sourceFiles = this.project.getSourceFiles();
71
+
72
+ // Extract all handlers
73
+ const handlers: MessageHandler[] = [];
74
+ const messageTypeNames = new Set<string>();
75
+ const emitterNames = new Set<string>();
76
+
77
+ for (const sourceFile of sourceFiles) {
78
+ const fileHandlers = this.extractHandlersFromFile(sourceFile);
79
+ handlers.push(...fileHandlers);
80
+
81
+ for (const handler of fileHandlers) {
82
+ messageTypeNames.add(handler.messageType);
83
+ emitterNames.add(handler.node);
84
+ }
85
+ }
86
+
87
+ // Define nodes (emitters)
88
+ const nodes: NodeDefinition[] = [];
89
+
90
+ // Central event bus node (mediator pattern)
91
+ nodes.push({
92
+ id: "central-bus",
93
+ type: "event-bus",
94
+ canSendTo: ["*"],
95
+ canReceiveFrom: ["*"],
96
+ metadata: {
97
+ isMediator: true,
98
+ },
99
+ });
100
+
101
+ // Individual emitter nodes
102
+ for (const emitterName of emitterNames) {
103
+ if (emitterName !== "central-bus") {
104
+ nodes.push({
105
+ id: emitterName,
106
+ type: "emitter",
107
+ canSendTo: ["central-bus", "*"],
108
+ canReceiveFrom: ["central-bus", "*"],
109
+ metadata: {
110
+ isEmitter: true,
111
+ },
112
+ });
113
+ }
114
+ }
115
+
116
+ // Define message types
117
+ const messageTypes: MessageType[] = Array.from(messageTypeNames).map((name) => ({
118
+ name,
119
+ payload: {
120
+ name: "unknown",
121
+ kind: "unknown",
122
+ nullable: false,
123
+ },
124
+ routing: {
125
+ from: ["*"],
126
+ to: ["*"],
127
+ },
128
+ }));
129
+
130
+ // Define routing rules (event bus uses broadcast pattern)
131
+ const routingRules: RoutingRule[] = [
132
+ {
133
+ pattern: "broadcast",
134
+ messageTypes: Array.from(messageTypeNames),
135
+ description: "Event bus broadcast pattern - one-to-many messaging",
136
+ },
137
+ ];
138
+
139
+ return {
140
+ nodes,
141
+ messageTypes,
142
+ routingRules,
143
+ state: {}, // Populated by user configuration
144
+ handlers,
145
+ bounds: {
146
+ maxConcurrentMessages: this.config.maxInFlight!,
147
+ maxNodes: Math.min(nodes.length, this.config.maxEmitters! + 1), // +1 for central bus
148
+ },
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Recognize message handler registration: emitter.on("event", handler)
154
+ */
155
+ recognizeMessageHandler(node: Node): MessageHandler | null {
156
+ if (!Node.isCallExpression(node)) {
157
+ return null;
158
+ }
159
+
160
+ const expression = node.getExpression();
161
+
162
+ // Check if this is a .on() or .addListener() call
163
+ if (!Node.isPropertyAccessExpression(expression)) {
164
+ return null;
165
+ }
166
+
167
+ const methodName = expression.getName();
168
+ if (methodName !== "on" && methodName !== "addListener" && methodName !== "addEventListener") {
169
+ return null;
170
+ }
171
+
172
+ return this.extractHandlerFromOnCall(node);
173
+ }
174
+
175
+ /**
176
+ * Recognize state mutation: state.field = value
177
+ */
178
+ recognizeStateUpdate(node: Node): StateAssignment | null {
179
+ if (!Node.isBinaryExpression(node)) {
180
+ return null;
181
+ }
182
+
183
+ const operator = node.getOperatorToken().getText();
184
+ if (operator !== "=") {
185
+ return null;
186
+ }
187
+
188
+ const left = node.getLeft();
189
+ const right = node.getRight();
190
+
191
+ // Check if left side is a state property access
192
+ if (!Node.isPropertyAccessExpression(left)) {
193
+ return null;
194
+ }
195
+
196
+ const fieldPath = this.getPropertyPath(left);
197
+
198
+ // Check if this is a state access
199
+ if (!fieldPath.startsWith("state.")) {
200
+ return null;
201
+ }
202
+
203
+ const field = fieldPath.substring(6); // Remove "state." prefix
204
+ const value = this.extractValue(right);
205
+
206
+ if (value === undefined) {
207
+ return null;
208
+ }
209
+
210
+ return {
211
+ field,
212
+ value,
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Recognize verification condition: requires() or ensures()
218
+ */
219
+ recognizeVerificationCondition(
220
+ node: Node,
221
+ type: "precondition" | "postcondition"
222
+ ): VerificationCondition | null {
223
+ if (!Node.isCallExpression(node)) {
224
+ return null;
225
+ }
226
+
227
+ const callee = node.getExpression();
228
+ if (!Node.isIdentifier(callee)) {
229
+ return null;
230
+ }
231
+
232
+ const functionName = callee.getText();
233
+ const expectedName = type === "precondition" ? "requires" : "ensures";
234
+
235
+ if (functionName !== expectedName) {
236
+ return null;
237
+ }
238
+
239
+ return this.extractCondition(node);
240
+ }
241
+
242
+ // ─────────────────────────────────────────────────────────────────
243
+ // Private Helper Methods
244
+ // ─────────────────────────────────────────────────────────────────
245
+
246
+ /**
247
+ * Extract all handlers from a source file
248
+ */
249
+ private extractHandlersFromFile(sourceFile: SourceFile): MessageHandler[] {
250
+ const handlers: MessageHandler[] = [];
251
+
252
+ sourceFile.forEachDescendant((node) => {
253
+ const handler = this.recognizeMessageHandler(node);
254
+ if (handler) {
255
+ handlers.push(handler);
256
+ }
257
+ });
258
+
259
+ return handlers;
260
+ }
261
+
262
+ /**
263
+ * Extract handler details from a .on() call expression
264
+ */
265
+ private extractHandlerFromOnCall(callExpr: Node): MessageHandler | null {
266
+ if (!Node.isCallExpression(callExpr)) {
267
+ return null;
268
+ }
269
+
270
+ const args = callExpr.getArguments();
271
+
272
+ if (args.length < 2) {
273
+ return null;
274
+ }
275
+
276
+ // First argument should be the event name (string literal)
277
+ const eventNameArg = args[0];
278
+ let eventName: string | null = null;
279
+
280
+ if (Node.isStringLiteral(eventNameArg)) {
281
+ eventName = eventNameArg.getLiteralValue();
282
+ } else if (Node.isTemplateExpression(eventNameArg)) {
283
+ eventName = eventNameArg.getText().replace(/[`'"]/g, "");
284
+ }
285
+
286
+ if (!eventName) {
287
+ return null;
288
+ }
289
+
290
+ // Second argument is the handler function
291
+ const handlerArg = args[1];
292
+ const assignments: StateAssignment[] = [];
293
+ const preconditions: VerificationCondition[] = [];
294
+ const postconditions: VerificationCondition[] = [];
295
+
296
+ // Parse the handler function for state assignments and verification conditions
297
+ if (Node.isArrowFunction(handlerArg) || Node.isFunctionExpression(handlerArg)) {
298
+ this.extractAssignmentsFromFunction(handlerArg, assignments);
299
+ this.extractVerificationConditionsFromFunction(handlerArg, preconditions, postconditions);
300
+ }
301
+
302
+ // Infer emitter name from the call expression
303
+ const expression = callExpr.getExpression();
304
+ let emitterName = "central-bus";
305
+ if (Node.isPropertyAccessExpression(expression)) {
306
+ const emitterExpr = expression.getExpression();
307
+ if (Node.isIdentifier(emitterExpr)) {
308
+ emitterName = emitterExpr.getText();
309
+ }
310
+ }
311
+
312
+ const sourceFile = callExpr.getSourceFile();
313
+ const line = callExpr.getStartLineNumber();
314
+
315
+ return {
316
+ messageType: eventName,
317
+ node: emitterName,
318
+ assignments,
319
+ preconditions,
320
+ postconditions,
321
+ location: {
322
+ file: sourceFile.getFilePath(),
323
+ line,
324
+ },
325
+ };
326
+ }
327
+
328
+ /**
329
+ * Extract state assignments from a handler function
330
+ */
331
+ private extractAssignmentsFromFunction(funcNode: Node, assignments: StateAssignment[]): void {
332
+ funcNode.forEachDescendant((node) => {
333
+ const assignment = this.recognizeStateUpdate(node);
334
+ if (assignment) {
335
+ assignments.push(assignment);
336
+ }
337
+ });
338
+ }
339
+
340
+ /**
341
+ * Extract verification conditions from a handler function
342
+ */
343
+ private extractVerificationConditionsFromFunction(
344
+ funcNode: Node,
345
+ preconditions: VerificationCondition[],
346
+ postconditions: VerificationCondition[]
347
+ ): void {
348
+ const body =
349
+ Node.isArrowFunction(funcNode) || Node.isFunctionExpression(funcNode)
350
+ ? funcNode.getBody()
351
+ : funcNode;
352
+
353
+ if (!body) {
354
+ return;
355
+ }
356
+
357
+ // Get all statements in the function body
358
+ const statements = Node.isBlock(body) ? body.getStatements() : [body];
359
+
360
+ for (const statement of statements) {
361
+ // Look for expression statements that are function calls
362
+ if (Node.isExpressionStatement(statement)) {
363
+ const expr = statement.getExpression();
364
+
365
+ const precond = this.recognizeVerificationCondition(expr, "precondition");
366
+ if (precond) {
367
+ preconditions.push(precond);
368
+ }
369
+
370
+ const postcond = this.recognizeVerificationCondition(expr, "postcondition");
371
+ if (postcond) {
372
+ postconditions.push(postcond);
373
+ }
374
+ }
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Extract condition from a requires() or ensures() call
380
+ */
381
+ private extractCondition(callExpr: Node): VerificationCondition | null {
382
+ if (!Node.isCallExpression(callExpr)) {
383
+ return null;
384
+ }
385
+
386
+ const args = callExpr.getArguments();
387
+
388
+ if (args.length === 0) {
389
+ return null;
390
+ }
391
+
392
+ // First argument is the condition expression
393
+ const conditionArg = args[0];
394
+ const expression = conditionArg.getText();
395
+
396
+ // Second argument (optional) is the message
397
+ let message: string | undefined;
398
+ if (args.length >= 2 && Node.isStringLiteral(args[1])) {
399
+ message = args[1].getLiteralValue();
400
+ }
401
+
402
+ const line = callExpr.getStartLineNumber();
403
+ const column = callExpr.getStart();
404
+
405
+ return {
406
+ expression,
407
+ message,
408
+ location: {
409
+ line,
410
+ column,
411
+ },
412
+ };
413
+ }
414
+
415
+ /**
416
+ * Get the full property access path (e.g., "state.user.loggedIn")
417
+ */
418
+ private getPropertyPath(node: Node): string {
419
+ const parts: string[] = [];
420
+
421
+ let current: Node = node;
422
+ while (Node.isPropertyAccessExpression(current)) {
423
+ parts.unshift(current.getName());
424
+ current = current.getExpression();
425
+ }
426
+
427
+ // Add the base identifier
428
+ if (Node.isIdentifier(current)) {
429
+ parts.unshift(current.getText());
430
+ }
431
+
432
+ return parts.join(".");
433
+ }
434
+
435
+ /**
436
+ * Extract a literal value from an expression
437
+ */
438
+ private extractValue(node: Node): string | boolean | number | null | undefined {
439
+ if (Node.isStringLiteral(node)) {
440
+ return node.getLiteralValue();
441
+ }
442
+
443
+ if (Node.isNumericLiteral(node)) {
444
+ return node.getLiteralValue();
445
+ }
446
+
447
+ if (node.getKind() === SyntaxKind.TrueKeyword) {
448
+ return true;
449
+ }
450
+
451
+ if (node.getKind() === SyntaxKind.FalseKeyword) {
452
+ return false;
453
+ }
454
+
455
+ if (node.getKind() === SyntaxKind.NullKeyword) {
456
+ return null;
457
+ }
458
+
459
+ // For complex expressions, return undefined (can't extract)
460
+ return undefined;
461
+ }
462
+
463
+ /**
464
+ * Custom invariants specific to event buses
465
+ */
466
+ customInvariants(): Array<[name: string, tlaExpression: string]> {
467
+ return [
468
+ [
469
+ "CentralBusAlwaysAvailable",
470
+ 'ports["central-bus"] = "connected" \\* Central event bus should always be available',
471
+ ],
472
+ [
473
+ "BroadcastOrdering",
474
+ "\\A msg1, msg2 \\in Range(messages) : " +
475
+ '(msg1.msgType = msg2.msgType /\\ msg1.status = "delivered" /\\ msg2.status = "pending") => ' +
476
+ "msg1.id < msg2.id \\* Events of the same type are delivered in order",
477
+ ],
478
+ ];
479
+ }
480
+ }