@fairfox/polly 0.1.3 → 0.1.4

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 (68) hide show
  1. package/{cli/polly.ts → dist/cli/polly.js} +100 -206
  2. package/dist/cli/polly.js.map +10 -0
  3. package/dist/scripts/build-extension.js +137 -0
  4. package/dist/scripts/build-extension.js.map +10 -0
  5. package/dist/vendor/verify/src/cli.js +2089 -0
  6. package/dist/vendor/verify/src/cli.js.map +16 -0
  7. package/dist/vendor/visualize/src/cli.js +2204 -0
  8. package/dist/vendor/visualize/src/cli.js.map +19 -0
  9. package/package.json +12 -12
  10. package/vendor/analysis/src/extract/adr.ts +0 -212
  11. package/vendor/analysis/src/extract/architecture.ts +0 -160
  12. package/vendor/analysis/src/extract/contexts.ts +0 -298
  13. package/vendor/analysis/src/extract/flows.ts +0 -309
  14. package/vendor/analysis/src/extract/handlers.ts +0 -321
  15. package/vendor/analysis/src/extract/index.ts +0 -9
  16. package/vendor/analysis/src/extract/integrations.ts +0 -329
  17. package/vendor/analysis/src/extract/manifest.ts +0 -298
  18. package/vendor/analysis/src/extract/types.ts +0 -389
  19. package/vendor/analysis/src/index.ts +0 -7
  20. package/vendor/analysis/src/types/adr.ts +0 -53
  21. package/vendor/analysis/src/types/architecture.ts +0 -245
  22. package/vendor/analysis/src/types/core.ts +0 -210
  23. package/vendor/analysis/src/types/index.ts +0 -18
  24. package/vendor/verify/src/adapters/base.ts +0 -164
  25. package/vendor/verify/src/adapters/detection.ts +0 -281
  26. package/vendor/verify/src/adapters/event-bus/index.ts +0 -480
  27. package/vendor/verify/src/adapters/web-extension/index.ts +0 -508
  28. package/vendor/verify/src/adapters/websocket/index.ts +0 -486
  29. package/vendor/verify/src/cli.ts +0 -430
  30. package/vendor/verify/src/codegen/config.ts +0 -354
  31. package/vendor/verify/src/codegen/tla.ts +0 -719
  32. package/vendor/verify/src/config/parser.ts +0 -303
  33. package/vendor/verify/src/config/types.ts +0 -113
  34. package/vendor/verify/src/core/model.ts +0 -267
  35. package/vendor/verify/src/core/primitives.ts +0 -106
  36. package/vendor/verify/src/extract/handlers.ts +0 -2
  37. package/vendor/verify/src/extract/types.ts +0 -2
  38. package/vendor/verify/src/index.ts +0 -150
  39. package/vendor/verify/src/primitives/index.ts +0 -102
  40. package/vendor/verify/src/runner/docker.ts +0 -283
  41. package/vendor/verify/src/types.ts +0 -51
  42. package/vendor/visualize/src/cli.ts +0 -365
  43. package/vendor/visualize/src/codegen/structurizr.ts +0 -770
  44. package/vendor/visualize/src/index.ts +0 -13
  45. package/vendor/visualize/src/runner/export.ts +0 -235
  46. package/vendor/visualize/src/viewer/server.ts +0 -485
  47. /package/dist/{background → src/background}/index.js +0 -0
  48. /package/dist/{background → src/background}/index.js.map +0 -0
  49. /package/dist/{background → src/background}/message-router.js +0 -0
  50. /package/dist/{background → src/background}/message-router.js.map +0 -0
  51. /package/dist/{index.js → src/index.js} +0 -0
  52. /package/dist/{index.js.map → src/index.js.map} +0 -0
  53. /package/dist/{shared → src/shared}/adapters/index.js +0 -0
  54. /package/dist/{shared → src/shared}/adapters/index.js.map +0 -0
  55. /package/dist/{shared → src/shared}/lib/context-helpers.js +0 -0
  56. /package/dist/{shared → src/shared}/lib/context-helpers.js.map +0 -0
  57. /package/dist/{shared → src/shared}/lib/errors.js +0 -0
  58. /package/dist/{shared → src/shared}/lib/errors.js.map +0 -0
  59. /package/dist/{shared → src/shared}/lib/message-bus.js +0 -0
  60. /package/dist/{shared → src/shared}/lib/message-bus.js.map +0 -0
  61. /package/dist/{shared → src/shared}/lib/state.js +0 -0
  62. /package/dist/{shared → src/shared}/lib/state.js.map +0 -0
  63. /package/dist/{shared → src/shared}/lib/test-helpers.js +0 -0
  64. /package/dist/{shared → src/shared}/lib/test-helpers.js.map +0 -0
  65. /package/dist/{shared → src/shared}/state/app-state.js +0 -0
  66. /package/dist/{shared → src/shared}/state/app-state.js.map +0 -0
  67. /package/dist/{shared → src/shared}/types/messages.js +0 -0
  68. /package/dist/{shared → src/shared}/types/messages.js.map +0 -0
@@ -1,486 +0,0 @@
1
- // ═══════════════════════════════════════════════════════════════
2
- // WebSocket Adapter
3
- // ═══════════════════════════════════════════════════════════════
4
- //
5
- // Extracts verification model from Bun WebSocket applications.
6
- // Recognizes:
7
- // - WebSocket server/client communication
8
- // - Eden type definitions for messages
9
- // - Handler functions (handle* pattern)
10
- // - State mutations via global state object
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 WebSocketAdapterConfig extends AdapterConfig {
30
- /** Whether to use Eden types for message extraction */
31
- useEdenTypes?: boolean;
32
-
33
- /** Pattern to match handler functions (default: /^handle[A-Z]/) */
34
- handlerPattern?: RegExp;
35
-
36
- /** Maximum concurrent connections to model (default: 10) */
37
- maxConnections?: number;
38
-
39
- /** Maximum messages in flight (default: 5) */
40
- maxInFlight?: number;
41
- }
42
-
43
- // ─────────────────────────────────────────────────────────────────
44
- // WebSocket Adapter Implementation
45
- // ─────────────────────────────────────────────────────────────────
46
-
47
- export class WebSocketAdapter implements RoutingAdapter<WebSocketAdapterConfig> {
48
- readonly name = "websocket";
49
- readonly config: WebSocketAdapterConfig;
50
- private project: Project;
51
-
52
- constructor(config: WebSocketAdapterConfig) {
53
- this.config = {
54
- useEdenTypes: true,
55
- handlerPattern: /^handle[A-Z]/,
56
- maxConnections: 10,
57
- maxInFlight: 5,
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 WebSocket application
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
-
76
- for (const sourceFile of sourceFiles) {
77
- const fileHandlers = this.extractHandlersFromFile(sourceFile);
78
- handlers.push(...fileHandlers);
79
-
80
- for (const handler of fileHandlers) {
81
- messageTypeNames.add(handler.messageType);
82
- }
83
- }
84
-
85
- // Define nodes (server + clients)
86
- const nodes: NodeDefinition[] = [
87
- {
88
- id: "server",
89
- type: "websocket-server",
90
- canSendTo: ["*"], // Server can send to all clients
91
- canReceiveFrom: ["*"], // Server receives from all clients
92
- metadata: {
93
- isHub: true,
94
- },
95
- },
96
- ];
97
-
98
- // Add client nodes (bounded for model checking)
99
- for (let i = 1; i <= this.config.maxConnections!; i++) {
100
- nodes.push({
101
- id: `client-${i}`,
102
- type: "websocket-client",
103
- canSendTo: ["server"], // Clients send to server
104
- canReceiveFrom: ["server"], // Clients receive from server
105
- metadata: {
106
- clientId: i,
107
- },
108
- });
109
- }
110
-
111
- // Define message types
112
- const messageTypes: MessageType[] = Array.from(messageTypeNames).map((name) => ({
113
- name,
114
- payload: {
115
- name: "unknown",
116
- kind: "unknown",
117
- nullable: false,
118
- },
119
- routing: {
120
- from:
121
- name.startsWith("USER_") ||
122
- name.startsWith("CHAT_") ||
123
- name.startsWith("TODO_") ||
124
- name.startsWith("SYNC_")
125
- ? ["client-*"] // Client messages
126
- : ["server"], // Server messages
127
- to:
128
- name.startsWith("USER_") ||
129
- name.startsWith("CHAT_") ||
130
- name.startsWith("TODO_") ||
131
- name.startsWith("SYNC_")
132
- ? ["server"] // To server
133
- : ["client-*"], // To clients
134
- },
135
- }));
136
-
137
- // Define routing rules (hub-and-spoke pattern)
138
- const routingRules: RoutingRule[] = [
139
- {
140
- pattern: "request-reply",
141
- messageTypes: Array.from(messageTypeNames),
142
- description:
143
- "WebSocket hub-and-spoke: clients send to server, server broadcasts to clients",
144
- },
145
- ];
146
-
147
- return {
148
- nodes,
149
- messageTypes,
150
- routingRules,
151
- state: {}, // Populated by user configuration
152
- handlers,
153
- bounds: {
154
- maxConcurrentMessages: this.config.maxInFlight!,
155
- maxNodes: nodes.length,
156
- custom: {
157
- maxConnections: this.config.maxConnections!,
158
- },
159
- },
160
- };
161
- }
162
-
163
- /**
164
- * Recognize message handler: handle* functions
165
- */
166
- recognizeMessageHandler(node: Node): MessageHandler | null {
167
- if (!Node.isFunctionDeclaration(node)) {
168
- return null;
169
- }
170
-
171
- const name = node.getName();
172
- if (!name || !this.config.handlerPattern!.test(name)) {
173
- return null;
174
- }
175
-
176
- return this.extractHandlerFromFunction(node);
177
- }
178
-
179
- /**
180
- * Recognize state mutation: state.field = value
181
- */
182
- recognizeStateUpdate(node: Node): StateAssignment | null {
183
- if (!Node.isBinaryExpression(node)) {
184
- return null;
185
- }
186
-
187
- const operator = node.getOperatorToken().getText();
188
- if (operator !== "=" && operator !== "+=" && operator !== "-=") {
189
- return null;
190
- }
191
-
192
- const left = node.getLeft();
193
- const right = node.getRight();
194
-
195
- // Check if left side is a state property access
196
- if (!Node.isPropertyAccessExpression(left)) {
197
- return null;
198
- }
199
-
200
- const fieldPath = this.getPropertyPath(left);
201
-
202
- // Check if this is a state access
203
- if (!fieldPath.startsWith("state.")) {
204
- return null;
205
- }
206
-
207
- const field = fieldPath.substring(6); // Remove "state." prefix
208
-
209
- // For += and -= operators, we can't extract the exact value
210
- if (operator !== "=") {
211
- return null;
212
- }
213
-
214
- const value = this.extractValue(right);
215
-
216
- if (value === undefined) {
217
- return null;
218
- }
219
-
220
- return {
221
- field,
222
- value,
223
- };
224
- }
225
-
226
- /**
227
- * Recognize verification condition: requires() or ensures()
228
- */
229
- recognizeVerificationCondition(
230
- node: Node,
231
- type: "precondition" | "postcondition"
232
- ): VerificationCondition | null {
233
- if (!Node.isCallExpression(node)) {
234
- return null;
235
- }
236
-
237
- const callee = node.getExpression();
238
- if (!Node.isIdentifier(callee)) {
239
- return null;
240
- }
241
-
242
- const functionName = callee.getText();
243
- const expectedName = type === "precondition" ? "requires" : "ensures";
244
-
245
- if (functionName !== expectedName) {
246
- return null;
247
- }
248
-
249
- return this.extractCondition(node);
250
- }
251
-
252
- // ─────────────────────────────────────────────────────────────────
253
- // Private Helper Methods
254
- // ─────────────────────────────────────────────────────────────────
255
-
256
- /**
257
- * Extract all handlers from a source file
258
- */
259
- private extractHandlersFromFile(sourceFile: SourceFile): MessageHandler[] {
260
- const handlers: MessageHandler[] = [];
261
-
262
- sourceFile.forEachDescendant((node) => {
263
- const handler = this.recognizeMessageHandler(node);
264
- if (handler) {
265
- handlers.push(handler);
266
- }
267
- });
268
-
269
- return handlers;
270
- }
271
-
272
- /**
273
- * Extract handler details from a handle* function
274
- */
275
- private extractHandlerFromFunction(funcNode: Node): MessageHandler | null {
276
- if (!Node.isFunctionDeclaration(funcNode)) {
277
- return null;
278
- }
279
-
280
- const name = funcNode.getName();
281
- if (!name) {
282
- return null;
283
- }
284
-
285
- // Extract message type from function name
286
- // handleUserJoin -> USER_JOIN
287
- const messageType = this.functionNameToMessageType(name);
288
-
289
- const assignments: StateAssignment[] = [];
290
- const preconditions: VerificationCondition[] = [];
291
- const postconditions: VerificationCondition[] = [];
292
-
293
- // Parse the function body
294
- this.extractAssignmentsFromFunction(funcNode, assignments);
295
- this.extractVerificationConditionsFromFunction(funcNode, preconditions, postconditions);
296
-
297
- const sourceFile = funcNode.getSourceFile();
298
- const line = funcNode.getStartLineNumber();
299
-
300
- return {
301
- messageType,
302
- node: "server", // All handlers run on server
303
- assignments,
304
- preconditions,
305
- postconditions,
306
- location: {
307
- file: sourceFile.getFilePath(),
308
- line,
309
- },
310
- };
311
- }
312
-
313
- /**
314
- * Convert handler function name to message type
315
- * handleUserJoin -> USER_JOIN
316
- */
317
- private functionNameToMessageType(name: string): string {
318
- // Remove "handle" prefix
319
- const withoutHandle = name.replace(/^handle/, "");
320
-
321
- // Convert PascalCase to SCREAMING_SNAKE_CASE
322
- return withoutHandle
323
- .replace(/([A-Z])/g, "_$1")
324
- .replace(/^_/, "")
325
- .toUpperCase();
326
- }
327
-
328
- /**
329
- * Extract state assignments from a 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 function
342
- */
343
- private extractVerificationConditionsFromFunction(
344
- funcNode: Node,
345
- preconditions: VerificationCondition[],
346
- postconditions: VerificationCondition[]
347
- ): void {
348
- if (!Node.isFunctionDeclaration(funcNode)) {
349
- return;
350
- }
351
-
352
- const body = funcNode.getBody();
353
-
354
- if (!body || !Node.isBlock(body)) {
355
- return;
356
- }
357
-
358
- // Get all statements in the function body
359
- const statements = body.getStatements();
360
-
361
- for (const statement of statements) {
362
- // Look for expression statements that are function calls
363
- if (Node.isExpressionStatement(statement)) {
364
- const expr = statement.getExpression();
365
-
366
- const precond = this.recognizeVerificationCondition(expr, "precondition");
367
- if (precond) {
368
- preconditions.push(precond);
369
- }
370
-
371
- const postcond = this.recognizeVerificationCondition(expr, "postcondition");
372
- if (postcond) {
373
- postconditions.push(postcond);
374
- }
375
- }
376
- }
377
- }
378
-
379
- /**
380
- * Extract condition from a requires() or ensures() call
381
- */
382
- private extractCondition(callExpr: Node): VerificationCondition | null {
383
- if (!Node.isCallExpression(callExpr)) {
384
- return null;
385
- }
386
-
387
- const args = callExpr.getArguments();
388
-
389
- if (args.length === 0) {
390
- return null;
391
- }
392
-
393
- // First argument is the condition expression
394
- const conditionArg = args[0];
395
- const expression = conditionArg.getText();
396
-
397
- // Second argument (optional) is the message
398
- let message: string | undefined;
399
- if (args.length >= 2 && Node.isStringLiteral(args[1])) {
400
- message = args[1].getLiteralValue();
401
- }
402
-
403
- const line = callExpr.getStartLineNumber();
404
- const column = callExpr.getStart();
405
-
406
- return {
407
- expression,
408
- message,
409
- location: {
410
- line,
411
- column,
412
- },
413
- };
414
- }
415
-
416
- /**
417
- * Get the full property access path (e.g., "state.user.loggedIn")
418
- */
419
- private getPropertyPath(node: Node): string {
420
- const parts: string[] = [];
421
-
422
- let current: Node = node;
423
- while (Node.isPropertyAccessExpression(current)) {
424
- parts.unshift(current.getName());
425
- current = current.getExpression();
426
- }
427
-
428
- // Add the base identifier
429
- if (Node.isIdentifier(current)) {
430
- parts.unshift(current.getText());
431
- }
432
-
433
- return parts.join(".");
434
- }
435
-
436
- /**
437
- * Extract a literal value from an expression
438
- */
439
- private extractValue(node: Node): string | boolean | number | null | undefined {
440
- if (Node.isStringLiteral(node)) {
441
- return node.getLiteralValue();
442
- }
443
-
444
- if (Node.isNumericLiteral(node)) {
445
- return node.getLiteralValue();
446
- }
447
-
448
- if (node.getKind() === SyntaxKind.TrueKeyword) {
449
- return true;
450
- }
451
-
452
- if (node.getKind() === SyntaxKind.FalseKeyword) {
453
- return false;
454
- }
455
-
456
- if (node.getKind() === SyntaxKind.NullKeyword) {
457
- return null;
458
- }
459
-
460
- // For complex expressions, return undefined (can't extract)
461
- return undefined;
462
- }
463
-
464
- /**
465
- * Custom invariants specific to WebSocket systems
466
- */
467
- customInvariants(): Array<[name: string, tlaExpression: string]> {
468
- return [
469
- [
470
- "ServerAlwaysAvailable",
471
- 'ports["server"] = "connected" \\* Server must always be available',
472
- ],
473
- [
474
- "ClientsConnectToServer",
475
- "\\A msg \\in Range(messages) : " +
476
- '(msg.source # "server") => ("server" \\in msg.targets) \\* Clients must route through server',
477
- ],
478
- [
479
- "BroadcastConsistency",
480
- "\\A c1, c2 \\in Contexts : " +
481
- '(c1 # "server" /\\ c2 # "server" /\\ ports[c1] = "connected" /\\ ports[c2] = "connected") => ' +
482
- "\\* All connected clients eventually receive broadcasts",
483
- ],
484
- ];
485
- }
486
- }