@fairfox/polly 0.2.0 → 0.2.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/dist/vendor/verify/specs/Dockerfile +13 -0
- package/dist/vendor/verify/specs/README.md +37 -0
- package/dist/vendor/verify/specs/docker-compose.yml +9 -0
- package/dist/vendor/verify/specs/tla/MessageRouter.cfg +24 -0
- package/dist/vendor/verify/specs/tla/MessageRouter.tla +221 -0
- package/dist/vendor/verify/specs/tla/README.md +179 -0
- package/dist/vendor/verify/specs/verification.config.ts +61 -0
- package/dist/vendor/verify/src/cli.js +22 -4
- package/dist/vendor/verify/src/cli.js.map +5 -5
- package/dist/vendor/visualize/src/cli.js +379 -46
- package/dist/vendor/visualize/src/cli.js.map +11 -10
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Verification Specifications
|
|
2
|
+
|
|
3
|
+
This directory contains formal specifications and verification configurations for the `@fairfox/polly-verify` package.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
### `verification.config.ts`
|
|
8
|
+
Example verification configuration demonstrating how to use the verify package with web extensions.
|
|
9
|
+
|
|
10
|
+
### `tla/`
|
|
11
|
+
TLA+ specifications for formal verification:
|
|
12
|
+
- `MessageRouter.tla` - TLA+ specification of the message routing system
|
|
13
|
+
- `MessageRouter.cfg` - TLC model checker configuration
|
|
14
|
+
- `README.md` - Documentation for running TLA+ verification
|
|
15
|
+
|
|
16
|
+
### Docker Setup
|
|
17
|
+
- `Dockerfile` - Container setup for TLA+ toolchain
|
|
18
|
+
- `docker-compose.yml` - Docker Compose configuration for running verification
|
|
19
|
+
|
|
20
|
+
## Running Verification
|
|
21
|
+
|
|
22
|
+
### With Docker (Recommended)
|
|
23
|
+
```bash
|
|
24
|
+
cd packages/verify/specs
|
|
25
|
+
docker-compose up
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### With TLC Directly
|
|
29
|
+
```bash
|
|
30
|
+
cd packages/verify/specs/tla
|
|
31
|
+
tlc MessageRouter.tla -config MessageRouter.cfg
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## See Also
|
|
35
|
+
- [Verify Package Documentation](../README.md)
|
|
36
|
+
- [WebSocket Example](../examples/websocket-app/README.md)
|
|
37
|
+
- [TLA+ Specifications](./tla/README.md)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
SPECIFICATION Spec
|
|
2
|
+
|
|
3
|
+
\* Constants
|
|
4
|
+
CONSTANTS
|
|
5
|
+
Contexts = {background, content, popup}
|
|
6
|
+
MaxMessages = 4
|
|
7
|
+
MaxTabId = 2
|
|
8
|
+
TimeoutLimit = 3
|
|
9
|
+
|
|
10
|
+
\* Invariants to check
|
|
11
|
+
INVARIANTS
|
|
12
|
+
TypeOK
|
|
13
|
+
NoRoutingLoops
|
|
14
|
+
DeliveredWerePending
|
|
15
|
+
NoOrphanedRequests
|
|
16
|
+
|
|
17
|
+
\* Properties to check
|
|
18
|
+
PROPERTIES
|
|
19
|
+
EventualResolution
|
|
20
|
+
ConnectedEventuallyDelivers
|
|
21
|
+
|
|
22
|
+
\* State constraint (keep state space manageable)
|
|
23
|
+
CONSTRAINT
|
|
24
|
+
Len(messages) <= MaxMessages
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
------------------------- MODULE MessageRouter -------------------------
|
|
2
|
+
(*
|
|
3
|
+
Formal specification of the web extension MessageRouter.
|
|
4
|
+
|
|
5
|
+
This spec models the core message routing behavior across extension contexts:
|
|
6
|
+
- Background service worker (central hub)
|
|
7
|
+
- Content scripts (one per tab)
|
|
8
|
+
- DevTools, Popup, Options (UI contexts)
|
|
9
|
+
|
|
10
|
+
Key properties verified:
|
|
11
|
+
1. No routing loops
|
|
12
|
+
2. Messages eventually deliver (if port connected)
|
|
13
|
+
3. All pending requests eventually resolve (success/timeout/disconnect)
|
|
14
|
+
4. Port cleanup is complete
|
|
15
|
+
*)
|
|
16
|
+
|
|
17
|
+
EXTENDS Integers, Sequences, FiniteSets, TLC
|
|
18
|
+
|
|
19
|
+
CONSTANTS
|
|
20
|
+
Contexts, \* Set of all contexts: {"background", "content", "popup", ...}
|
|
21
|
+
MaxMessages, \* Bound on number of messages (for model checking)
|
|
22
|
+
MaxTabId, \* Maximum tab ID
|
|
23
|
+
TimeoutLimit \* Message timeout threshold
|
|
24
|
+
|
|
25
|
+
VARIABLES
|
|
26
|
+
ports, \* Port state: [context -> "connected" | "disconnected"]
|
|
27
|
+
messages, \* Sequence of messages in flight
|
|
28
|
+
pendingRequests, \* Map: messageId -> {sender, target, timestamp}
|
|
29
|
+
delivered, \* Set of delivered message IDs
|
|
30
|
+
routingDepth, \* Current routing depth (for loop detection)
|
|
31
|
+
time \* Logical clock
|
|
32
|
+
|
|
33
|
+
vars == <<ports, messages, pendingRequests, delivered, routingDepth, time>>
|
|
34
|
+
|
|
35
|
+
-----------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
(* Type definitions *)
|
|
38
|
+
|
|
39
|
+
ContextType == {"background", "content", "popup", "devtools", "options", "offscreen"}
|
|
40
|
+
PortState == {"connected", "disconnected"}
|
|
41
|
+
MessageStatus == {"pending", "routing", "delivered", "failed", "timeout"}
|
|
42
|
+
|
|
43
|
+
Message == [
|
|
44
|
+
id: Nat,
|
|
45
|
+
source: ContextType,
|
|
46
|
+
targets: SUBSET ContextType, \* Set of target contexts (can be multiple)
|
|
47
|
+
tabId: Nat,
|
|
48
|
+
msgType: STRING, \* Message type for handler dispatch
|
|
49
|
+
status: MessageStatus,
|
|
50
|
+
timestamp: Nat
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
-----------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
(* Initial state *)
|
|
56
|
+
|
|
57
|
+
Init ==
|
|
58
|
+
/\ ports = [c \in Contexts |-> "disconnected"]
|
|
59
|
+
/\ messages = <<>>
|
|
60
|
+
/\ pendingRequests = [id \in {} |-> {}]
|
|
61
|
+
/\ delivered = {}
|
|
62
|
+
/\ routingDepth = 0
|
|
63
|
+
/\ time = 0
|
|
64
|
+
|
|
65
|
+
-----------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
(* Actions *)
|
|
68
|
+
|
|
69
|
+
(* A context connects a port *)
|
|
70
|
+
ConnectPort(context) ==
|
|
71
|
+
/\ ports[context] = "disconnected"
|
|
72
|
+
/\ ports' = [ports EXCEPT ![context] = "connected"]
|
|
73
|
+
/\ UNCHANGED <<messages, pendingRequests, delivered, routingDepth, time>>
|
|
74
|
+
|
|
75
|
+
(* A context disconnects *)
|
|
76
|
+
DisconnectPort(context) ==
|
|
77
|
+
/\ ports[context] = "connected"
|
|
78
|
+
/\ ports' = [ports EXCEPT ![context] = "disconnected"]
|
|
79
|
+
\* Clean up pending requests that target this context
|
|
80
|
+
/\ LET failedRequests == {id \in DOMAIN pendingRequests :
|
|
81
|
+
context \in pendingRequests[id].targets}
|
|
82
|
+
IN pendingRequests' = [id \in DOMAIN pendingRequests \ failedRequests |->
|
|
83
|
+
pendingRequests[id]]
|
|
84
|
+
/\ UNCHANGED <<messages, delivered, routingDepth, time>>
|
|
85
|
+
|
|
86
|
+
(* Send a message from source to one or more targets *)
|
|
87
|
+
SendMessage(source, targetSet, tabId, messageType) ==
|
|
88
|
+
/\ ports[source] = "connected"
|
|
89
|
+
/\ Len(messages) < MaxMessages
|
|
90
|
+
/\ routingDepth = 0 \* Only send at top level (no recursive sends)
|
|
91
|
+
/\ targetSet # {} \* Must have at least one target
|
|
92
|
+
/\ LET newId == Len(messages) + 1
|
|
93
|
+
newMsg == [
|
|
94
|
+
id |-> newId,
|
|
95
|
+
source |-> source,
|
|
96
|
+
targets |-> targetSet,
|
|
97
|
+
tabId |-> tabId,
|
|
98
|
+
msgType |-> messageType,
|
|
99
|
+
status |-> "pending",
|
|
100
|
+
timestamp |-> time
|
|
101
|
+
]
|
|
102
|
+
IN /\ messages' = Append(messages, newMsg)
|
|
103
|
+
/\ pendingRequests' = pendingRequests @@
|
|
104
|
+
(newId :> [sender |-> source,
|
|
105
|
+
targets |-> targetSet,
|
|
106
|
+
timestamp |-> time])
|
|
107
|
+
/\ time' = time + 1
|
|
108
|
+
/\ UNCHANGED <<ports, delivered, routingDepth>>
|
|
109
|
+
|
|
110
|
+
(* Route a message to one of its targets *)
|
|
111
|
+
RouteMessage(msgIndex) ==
|
|
112
|
+
/\ msgIndex \in 1..Len(messages)
|
|
113
|
+
/\ LET msg == messages[msgIndex]
|
|
114
|
+
IN /\ msg.status = "pending"
|
|
115
|
+
/\ routingDepth' = routingDepth + 1
|
|
116
|
+
/\ routingDepth < 5 \* Safety bound: detect loops
|
|
117
|
+
/\ \* Non-deterministically choose a target from the targets set
|
|
118
|
+
\E target \in msg.targets :
|
|
119
|
+
/\ IF target \in Contexts /\ ports[target] = "connected"
|
|
120
|
+
THEN \* Successful delivery to this target
|
|
121
|
+
/\ messages' = [messages EXCEPT ![msgIndex].status = "delivered"]
|
|
122
|
+
/\ delivered' = delivered \union {msg.id}
|
|
123
|
+
/\ pendingRequests' = [id \in DOMAIN pendingRequests \ {msg.id} |->
|
|
124
|
+
pendingRequests[id]]
|
|
125
|
+
/\ time' = time + 1
|
|
126
|
+
ELSE \* Port not connected or invalid target, message fails
|
|
127
|
+
/\ messages' = [messages EXCEPT ![msgIndex].status = "failed"]
|
|
128
|
+
/\ pendingRequests' = [id \in DOMAIN pendingRequests \ {msg.id} |->
|
|
129
|
+
pendingRequests[id]]
|
|
130
|
+
/\ time' = time + 1
|
|
131
|
+
/\ UNCHANGED delivered
|
|
132
|
+
/\ UNCHANGED ports
|
|
133
|
+
|
|
134
|
+
(* Complete routing (reset depth) *)
|
|
135
|
+
CompleteRouting ==
|
|
136
|
+
/\ routingDepth > 0
|
|
137
|
+
/\ routingDepth' = 0
|
|
138
|
+
/\ UNCHANGED <<ports, messages, pendingRequests, delivered, time>>
|
|
139
|
+
|
|
140
|
+
(* Handle message timeout *)
|
|
141
|
+
TimeoutMessage(msgIndex) ==
|
|
142
|
+
/\ msgIndex \in 1..Len(messages)
|
|
143
|
+
/\ LET msg == messages[msgIndex]
|
|
144
|
+
IN /\ msg.status = "pending"
|
|
145
|
+
/\ time - msg.timestamp > TimeoutLimit
|
|
146
|
+
/\ messages' = [messages EXCEPT ![msgIndex].status = "timeout"]
|
|
147
|
+
/\ pendingRequests' = [id \in DOMAIN pendingRequests \ {msg.id} |->
|
|
148
|
+
pendingRequests[id]]
|
|
149
|
+
/\ time' = time + 1
|
|
150
|
+
/\ UNCHANGED <<ports, delivered, routingDepth>>
|
|
151
|
+
|
|
152
|
+
-----------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
(* Next state relation *)
|
|
155
|
+
|
|
156
|
+
Next ==
|
|
157
|
+
\/ \E c \in Contexts : ConnectPort(c)
|
|
158
|
+
\/ \E c \in Contexts : DisconnectPort(c)
|
|
159
|
+
\/ \E src \in Contexts : \E targetSet \in (SUBSET Contexts \ {{}}) : \E tab \in 0..MaxTabId : \E msgType \in {"msg1", "msg2"} : SendMessage(src, targetSet, tab, msgType)
|
|
160
|
+
\/ \E i \in 1..Len(messages) : RouteMessage(i)
|
|
161
|
+
\/ CompleteRouting
|
|
162
|
+
\/ \E i \in 1..Len(messages) : TimeoutMessage(i)
|
|
163
|
+
|
|
164
|
+
Spec == Init /\ [][Next]_vars /\ WF_vars(Next)
|
|
165
|
+
|
|
166
|
+
-----------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
(* Invariants *)
|
|
169
|
+
|
|
170
|
+
(* CRITICAL: No infinite routing loops *)
|
|
171
|
+
NoRoutingLoops ==
|
|
172
|
+
routingDepth < 3
|
|
173
|
+
|
|
174
|
+
(* All delivered messages were actually pending *)
|
|
175
|
+
DeliveredWerePending ==
|
|
176
|
+
\A msgId \in delivered :
|
|
177
|
+
\E i \in 1..Len(messages) : messages[i].id = msgId
|
|
178
|
+
|
|
179
|
+
(* No orphaned pending requests - every pending request has a corresponding message *)
|
|
180
|
+
NoOrphanedRequests ==
|
|
181
|
+
\A reqId \in DOMAIN pendingRequests :
|
|
182
|
+
\E i \in 1..Len(messages) :
|
|
183
|
+
/\ messages[i].id = reqId
|
|
184
|
+
/\ messages[i].status \in {"pending", "routing"}
|
|
185
|
+
|
|
186
|
+
(* Messages to disconnected ports eventually fail *)
|
|
187
|
+
DisconnectedPortsFail ==
|
|
188
|
+
\A i \in 1..Len(messages) :
|
|
189
|
+
LET msg == messages[i]
|
|
190
|
+
IN (msg.status = "pending" /\ \A target \in msg.targets : ports[target] = "disconnected")
|
|
191
|
+
=> (time - msg.timestamp > TimeoutLimit => msg.status \in {"failed", "timeout"})
|
|
192
|
+
|
|
193
|
+
(* Type invariant *)
|
|
194
|
+
TypeOK ==
|
|
195
|
+
/\ ports \in [Contexts -> PortState]
|
|
196
|
+
/\ \A i \in 1..Len(messages) :
|
|
197
|
+
/\ messages[i].source \in Contexts
|
|
198
|
+
/\ messages[i].targets \subseteq Contexts
|
|
199
|
+
/\ messages[i].targets # {}
|
|
200
|
+
/\ messages[i].status \in MessageStatus
|
|
201
|
+
/\ routingDepth >= 0
|
|
202
|
+
/\ time >= 0
|
|
203
|
+
|
|
204
|
+
-----------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
(* Temporal properties *)
|
|
207
|
+
|
|
208
|
+
(* Eventually, all messages resolve (deliver, fail, or timeout) *)
|
|
209
|
+
EventualResolution ==
|
|
210
|
+
\A i \in 1..Len(messages) :
|
|
211
|
+
messages[i].status = "pending"
|
|
212
|
+
~> messages[i].status \in {"delivered", "failed", "timeout"}
|
|
213
|
+
|
|
214
|
+
(* If ports are connected and message is sent to them, message eventually delivers *)
|
|
215
|
+
ConnectedEventuallyDelivers ==
|
|
216
|
+
\A i \in 1..Len(messages) :
|
|
217
|
+
LET msg == messages[i]
|
|
218
|
+
IN (msg.status = "pending" /\ \A target \in msg.targets : ports[target] = "connected")
|
|
219
|
+
~> (msg.status = "delivered")
|
|
220
|
+
|
|
221
|
+
=============================================================================
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# TLA+ Formal Specification for MessageRouter
|
|
2
|
+
|
|
3
|
+
This directory contains formal specifications for the web extension's message routing system using TLA+ (Temporal Logic of Actions).
|
|
4
|
+
|
|
5
|
+
## What is This?
|
|
6
|
+
|
|
7
|
+
TLA+ is a formal specification language for concurrent and distributed systems. It allows us to:
|
|
8
|
+
- **Model** the message routing logic mathematically
|
|
9
|
+
- **Verify** properties like "no routing loops" or "messages eventually deliver"
|
|
10
|
+
- **Find edge cases** that are hard to catch with traditional testing
|
|
11
|
+
- **Document** the system behavior precisely
|
|
12
|
+
|
|
13
|
+
## What We're Verifying
|
|
14
|
+
|
|
15
|
+
### Core Properties
|
|
16
|
+
|
|
17
|
+
1. **NoRoutingLoops** - Messages never create infinite routing cycles
|
|
18
|
+
2. **EventualResolution** - Every message eventually resolves (delivers, fails, or times out)
|
|
19
|
+
3. **ConnectedEventuallyDelivers** - Messages to connected ports eventually deliver
|
|
20
|
+
4. **NoOrphanedRequests** - Pending requests always have corresponding messages
|
|
21
|
+
5. **TypeOK** - All data structures maintain correct types
|
|
22
|
+
|
|
23
|
+
### System Model
|
|
24
|
+
|
|
25
|
+
The spec models:
|
|
26
|
+
- **Contexts**: background, content, popup, devtools, options, offscreen
|
|
27
|
+
- **Port lifecycle**: disconnected ⇒ connected ⇒ disconnected
|
|
28
|
+
- **Message states**: pending → routing → delivered/failed/timeout
|
|
29
|
+
- **Routing depth tracking** (for loop detection)
|
|
30
|
+
- **Timeout handling**
|
|
31
|
+
- **Broadcast semantics**
|
|
32
|
+
|
|
33
|
+
## Running the Model Checker
|
|
34
|
+
|
|
35
|
+
### Prerequisites
|
|
36
|
+
|
|
37
|
+
Docker must be running. The TLA+ toolchain runs in a container.
|
|
38
|
+
|
|
39
|
+
### Quick Start
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# From project root
|
|
43
|
+
bun run tla:up # Start container
|
|
44
|
+
bun run tla:check # Run model checker
|
|
45
|
+
bun run tla:down # Stop container
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Manual Commands
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Start the TLA+ container
|
|
52
|
+
docker-compose -f specs/docker-compose.yml up -d
|
|
53
|
+
|
|
54
|
+
# Run the model checker
|
|
55
|
+
docker-compose -f specs/docker-compose.yml exec tla tlc MessageRouter.tla
|
|
56
|
+
|
|
57
|
+
# Interactive shell (for exploring)
|
|
58
|
+
docker-compose -f specs/docker-compose.yml exec tla sh
|
|
59
|
+
|
|
60
|
+
# Stop the container
|
|
61
|
+
docker-compose -f specs/docker-compose.yml down
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Understanding the Output
|
|
65
|
+
|
|
66
|
+
### Success
|
|
67
|
+
```
|
|
68
|
+
TLC2 Version X.X.X
|
|
69
|
+
...
|
|
70
|
+
Model checking completed. No error has been found.
|
|
71
|
+
Estimates of the probability that TLC did not check all reachable states
|
|
72
|
+
because two distinct states had the same fingerprint:
|
|
73
|
+
calculated (optimistic): val = X.X%
|
|
74
|
+
...
|
|
75
|
+
Finished in XXms at (YYYY-MM-DD HH:MM:SS)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Violation Found
|
|
79
|
+
TLC will print:
|
|
80
|
+
- **Which invariant was violated**
|
|
81
|
+
- **The execution trace** (sequence of states) leading to the violation
|
|
82
|
+
- Each state shows the values of all variables
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
```
|
|
86
|
+
Error: Invariant NoRoutingLoops is violated.
|
|
87
|
+
|
|
88
|
+
State 1: <Initial State>
|
|
89
|
+
/\ ports = ...
|
|
90
|
+
/\ routingDepth = 0
|
|
91
|
+
...
|
|
92
|
+
|
|
93
|
+
State 2: <ConnectPort("background")>
|
|
94
|
+
/\ ports = [background |-> "connected", ...]
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
State 3: <SendMessage("background", "content", 1)>
|
|
98
|
+
...
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Editing the Spec
|
|
102
|
+
|
|
103
|
+
### Files
|
|
104
|
+
|
|
105
|
+
- **MessageRouter.tla** - Main specification (edit this)
|
|
106
|
+
- **MessageRouter.cfg** - Model configuration (constants, invariants to check)
|
|
107
|
+
|
|
108
|
+
### Workflow
|
|
109
|
+
|
|
110
|
+
1. Edit `.tla` file locally (syntax highlighting via VSCode TLA+ extension)
|
|
111
|
+
2. Save changes (files are volume-mounted into container)
|
|
112
|
+
3. Run `bun run tla:check`
|
|
113
|
+
4. Iterate on violations
|
|
114
|
+
|
|
115
|
+
### Expanding the Model
|
|
116
|
+
|
|
117
|
+
Current model is intentionally simple (3 contexts, 4 messages max). To expand:
|
|
118
|
+
|
|
119
|
+
1. **Add more contexts**: Edit `MessageRouter.cfg`:
|
|
120
|
+
```
|
|
121
|
+
Contexts = {background, content, popup, devtools, options}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
2. **Increase message limit**: (Warning: state space grows exponentially!)
|
|
125
|
+
```
|
|
126
|
+
MaxMessages = 6
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
3. **Add new properties**: Define in `.tla`, add to `.cfg` under `INVARIANTS` or `PROPERTIES`
|
|
130
|
+
|
|
131
|
+
4. **Model request/response pairing**: Extend `Message` type with response IDs
|
|
132
|
+
|
|
133
|
+
## Performance Notes
|
|
134
|
+
|
|
135
|
+
Model checking explores **all possible states**. State space grows exponentially with:
|
|
136
|
+
- Number of contexts
|
|
137
|
+
- Number of messages
|
|
138
|
+
- Number of tabs
|
|
139
|
+
|
|
140
|
+
Current settings (3 contexts, 4 messages) check ~10,000 states in <1 second.
|
|
141
|
+
|
|
142
|
+
Increasing to 5 contexts + 6 messages = ~1 million states = ~10 seconds.
|
|
143
|
+
|
|
144
|
+
## Relationship to Code
|
|
145
|
+
|
|
146
|
+
The spec is **not** the implementation - it's a mathematical model.
|
|
147
|
+
|
|
148
|
+
**Code → Spec mapping:**
|
|
149
|
+
- `MessageRouter.routeMessage()` → `RouteMessage` action
|
|
150
|
+
- `port.onDisconnect()` → `DisconnectPort` action
|
|
151
|
+
- `pendingRequests` Map → `pendingRequests` variable
|
|
152
|
+
- "no loops" tests → `NoRoutingLoops` invariant
|
|
153
|
+
|
|
154
|
+
**Use TLA+ to:**
|
|
155
|
+
1. Find edge cases → Add as unit tests
|
|
156
|
+
2. Verify design before implementing
|
|
157
|
+
3. Document complex concurrent behavior
|
|
158
|
+
|
|
159
|
+
**Don't use TLA+ for:**
|
|
160
|
+
- Testing implementation details (use bun test)
|
|
161
|
+
- Performance testing
|
|
162
|
+
- Browser API quirks
|
|
163
|
+
|
|
164
|
+
## Learning Resources
|
|
165
|
+
|
|
166
|
+
- [TLA+ Home](https://lamport.azurewebsites.net/tla/tla.html)
|
|
167
|
+
- [Learn TLA+](https://learntla.com/)
|
|
168
|
+
- [Practical TLA+](https://www.apress.com/gp/book/9781484238288) (book)
|
|
169
|
+
- [TLA+ Video Course](https://lamport.azurewebsites.net/video/videos.html)
|
|
170
|
+
|
|
171
|
+
## Next Steps
|
|
172
|
+
|
|
173
|
+
Potential expansions:
|
|
174
|
+
- [ ] Model request/response pairing with IDs
|
|
175
|
+
- [ ] Model port reconnection scenarios
|
|
176
|
+
- [ ] Add tabId-specific routing
|
|
177
|
+
- [ ] Model MessageBus interaction
|
|
178
|
+
- [ ] Verify broadcast reaches all ports exactly once
|
|
179
|
+
- [ ] Model race conditions during port disconnect
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// Verification Configuration
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// This file configures TLA+ verification for your extension.
|
|
6
|
+
// Some values are auto-configured, others need your input.
|
|
7
|
+
//
|
|
8
|
+
// Look for:
|
|
9
|
+
// • /* CONFIGURE */ - Replace with your value
|
|
10
|
+
// • /* REVIEW */ - Check the auto-generated value
|
|
11
|
+
// • null - Must be replaced with a concrete value
|
|
12
|
+
//
|
|
13
|
+
// Run 'bun verify' to check for incomplete configuration.
|
|
14
|
+
// Run 'bun verify --setup' for interactive help.
|
|
15
|
+
//
|
|
16
|
+
|
|
17
|
+
import { defineVerification } from '../src/index'
|
|
18
|
+
|
|
19
|
+
export default defineVerification({
|
|
20
|
+
state: {
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
messages: {
|
|
24
|
+
// Maximum messages in flight simultaneously across all contexts.
|
|
25
|
+
// Higher = more realistic concurrency, but exponentially slower.
|
|
26
|
+
//
|
|
27
|
+
// Recommended values:
|
|
28
|
+
// • 2-3: Fast verification (< 10 seconds)
|
|
29
|
+
// • 4-6: Balanced (10-60 seconds)
|
|
30
|
+
// • 8+: Thorough but slow (minutes)
|
|
31
|
+
//
|
|
32
|
+
// WARNING: State space grows exponentially! Start small.
|
|
33
|
+
maxInFlight: 3,
|
|
34
|
+
|
|
35
|
+
// Maximum tab IDs to model (content scripts are per-tab).
|
|
36
|
+
//
|
|
37
|
+
// Recommended:
|
|
38
|
+
// • 0-1: Most extensions (single tab or tab-agnostic)
|
|
39
|
+
// • 2-3: Multi-tab coordination
|
|
40
|
+
//
|
|
41
|
+
// Start with 0 or 1 for faster verification.
|
|
42
|
+
maxTabs: 1,
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// Verification behavior
|
|
46
|
+
// ─────────────────────
|
|
47
|
+
//
|
|
48
|
+
// onBuild: What to do during development builds
|
|
49
|
+
// • 'warn' - Show warnings but don't fail (recommended)
|
|
50
|
+
// • 'error' - Fail the build on violations
|
|
51
|
+
// • 'off' - Skip verification
|
|
52
|
+
//
|
|
53
|
+
onBuild: 'warn',
|
|
54
|
+
|
|
55
|
+
// onRelease: What to do during production builds
|
|
56
|
+
// • 'error' - Fail the build on violations (recommended)
|
|
57
|
+
// • 'warn' - Show warnings but don't fail
|
|
58
|
+
// • 'off' - Skip verification
|
|
59
|
+
//
|
|
60
|
+
onRelease: 'error',
|
|
61
|
+
})
|
|
@@ -1172,7 +1172,6 @@ async function analyzeCodebase(options) {
|
|
|
1172
1172
|
const extractor = new TypeExtractor(options.tsConfigPath);
|
|
1173
1173
|
return extractor.analyzeCodebase(options.stateFilePath);
|
|
1174
1174
|
}
|
|
1175
|
-
|
|
1176
1175
|
// vendor/verify/src/codegen/config.ts
|
|
1177
1176
|
class ConfigGenerator {
|
|
1178
1177
|
lines = [];
|
|
@@ -1721,6 +1720,7 @@ function validateConfig(configPath) {
|
|
|
1721
1720
|
}
|
|
1722
1721
|
|
|
1723
1722
|
// vendor/verify/src/cli.ts
|
|
1723
|
+
var __dirname = "/Users/AJT/projects/polly/packages/polly/vendor/verify/src";
|
|
1724
1724
|
var COLORS = {
|
|
1725
1725
|
reset: "\x1B[0m",
|
|
1726
1726
|
red: "\x1B[31m",
|
|
@@ -1935,12 +1935,30 @@ async function runFullVerification(configPath) {
|
|
|
1935
1935
|
const cfgPath = path3.join(specDir, "UserApp.cfg");
|
|
1936
1936
|
fs3.writeFileSync(specPath, spec);
|
|
1937
1937
|
fs3.writeFileSync(cfgPath, cfg);
|
|
1938
|
-
const
|
|
1939
|
-
|
|
1938
|
+
const possiblePaths = [
|
|
1939
|
+
path3.join(process.cwd(), "specs", "tla", "MessageRouter.tla"),
|
|
1940
|
+
path3.join(__dirname, "..", "specs", "tla", "MessageRouter.tla"),
|
|
1941
|
+
path3.join(__dirname, "..", "..", "specs", "tla", "MessageRouter.tla"),
|
|
1942
|
+
path3.join(process.cwd(), "external", "polly", "packages", "verify", "specs", "tla", "MessageRouter.tla"),
|
|
1943
|
+
path3.join(process.cwd(), "node_modules", "@fairfox", "polly-verify", "specs", "tla", "MessageRouter.tla")
|
|
1944
|
+
];
|
|
1945
|
+
let baseSpecPath = null;
|
|
1946
|
+
for (const candidatePath of possiblePaths) {
|
|
1947
|
+
if (fs3.existsSync(candidatePath)) {
|
|
1948
|
+
baseSpecPath = candidatePath;
|
|
1949
|
+
break;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
if (baseSpecPath) {
|
|
1940
1953
|
const destSpecPath = path3.join(specDir, "MessageRouter.tla");
|
|
1941
1954
|
fs3.copyFileSync(baseSpecPath, destSpecPath);
|
|
1955
|
+
console.log(color("✓ Copied MessageRouter.tla", COLORS.green));
|
|
1942
1956
|
} else {
|
|
1943
1957
|
console.log(color("⚠️ Warning: MessageRouter.tla not found, verification may fail", COLORS.yellow));
|
|
1958
|
+
console.log(color(` Searched in:`, COLORS.gray));
|
|
1959
|
+
for (const searchPath of possiblePaths) {
|
|
1960
|
+
console.log(color(` - ${searchPath}`, COLORS.gray));
|
|
1961
|
+
}
|
|
1944
1962
|
}
|
|
1945
1963
|
console.log(color("✓ Specification generated", COLORS.green));
|
|
1946
1964
|
console.log(color(` ${specPath}`, COLORS.gray));
|
|
@@ -2071,4 +2089,4 @@ Stack trace:`, COLORS.gray));
|
|
|
2071
2089
|
process.exit(1);
|
|
2072
2090
|
});
|
|
2073
2091
|
|
|
2074
|
-
//# debugId=
|
|
2092
|
+
//# debugId=F6E692DF29A2A67064756E2164756E21
|