@fairfox/web-ext-verify
Version:
Formal verification for message-passing systems using TLA+. Automatically generates TLA+ specifications from TypeScript code.
421 lines (316 loc) • 10.7 kB
Markdown
# @fairfox/web-ext-verify
Formal verification for **any message-passing system** using TLA+.
## Overview
This package automatically generates TLA+ specifications from your TypeScript types and verifies correctness properties about your application's message routing and state management.
**Works with:**
- ✅ Chrome Extensions (background, content, popup contexts)
- ✅ Event-driven systems (EventEmitter, mitt, eventemitter3)
- 📋 Web Workers / Service Workers _(coming soon)_
- 📋 Actor systems (XState actors, etc.) _(coming soon)_
- ✨ Custom message-passing systems (write your own adapter!)
## Features
- **Universal adapters** - Works with web extensions, event buses, workers, and custom systems
- **Type-driven verification** - Extracts types from TypeScript, generates TLA+ automatically
- **Comment-driven configuration** - Smart config generation with inline guidance
- **High-level primitives** - Express common patterns easily (requires, ensures, invariant, etc.)
- **Progressive enhancement** - Start simple, add detail as needed
- **Escape hatch** - Drop to raw TLA+ for complex properties
## Adapter Architecture
The verify package uses a **pluggable adapter system** to support different messaging paradigms:
```
┌─────────────────────────────────────────────────────┐
│ User Configuration Layer │
│ (defines domain-specific messaging patterns) │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Routing Model Adapters (Pluggable) │
│ • WebExtension • EventBus • Worker • Custom │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Core Verification Engine │
│ (domain-agnostic TLA+ generation & checking) │
└─────────────────────────────────────────────────────┘
```
### Available Adapters
**WebExtensionAdapter** - Verifies Chrome extensions
- Recognizes extension contexts (background, content, popup, etc.)
- Understands `bus.on(type, handler)` pattern
- Models tab-based routing
**EventBusAdapter** - Verifies event-driven systems
- Recognizes `emitter.on(event, handler)` pattern
- Works with Node.js EventEmitter, mitt, eventemitter3
- Models broadcast (one-to-many) routing
See [`examples/`](./examples/) for complete usage examples.
## Installation
```bash
# Using Bun (recommended)
bun add -D @fairfox/web-ext-verify
# Using npm
npm install --save-dev @fairfox/web-ext-verify
# Using pnpm
pnpm add -D @fairfox/web-ext-verify
# Using yarn
yarn add -D @fairfox/web-ext-verify
```
> **Note:** This is a development tool. Install as a dev dependency (`-D` flag).
## Quick Start
### Using the CLI
The easiest way to get started is with the interactive CLI:
```bash
# 1. Generate configuration by analyzing your codebase
bunx web-ext-verify --setup
# 2. Review and edit the generated config
# Edit: specs/verification.config.ts
# 3. Validate your configuration
bunx web-ext-verify --validate
# 4. Run verification
bunx web-ext-verify
```
The CLI will:
- ✅ Analyze your TypeScript code automatically
- ✅ Generate TLA+ specifications
- ✅ Run the TLC model checker
- ✅ Report any violations with counterexamples
### Example 1: Web Extension
```typescript
// specs/verification.config.ts
import { WebExtensionAdapter } from '@fairfox/web-ext-verify'
const adapter = new WebExtensionAdapter({
tsConfigPath: "./tsconfig.json",
contexts: ["background", "content", "popup"],
maxTabs: 2,
maxInFlight: 6,
})
export default {
adapter,
state: {
"user.role": { type: "enum", values: ["admin", "user", "guest"] },
"todos": { maxLength: 10 },
},
onBuild: "warn",
onRelease: "error",
}
```
### Example 2: Event Bus
```typescript
// specs/verification.config.ts
import { EventBusAdapter } from '@fairfox/web-ext-verify'
const adapter = new EventBusAdapter({
tsConfigPath: "./tsconfig.json",
emitterLibrary: "events",
maxInFlight: 5,
})
export default {
adapter,
state: {
"user.loggedIn": { type: "boolean" },
"notifications.count": { min: 0, max: 10 },
},
onBuild: "warn",
onRelease: "error",
}
```
### Setup Steps
#### 1. Choose Your Adapter
Pick the adapter that matches your messaging system (see Available Adapters above).
#### 2. Configure State Bounds
Define bounds for your state fields (see Configuration Guide below).
#### 3. Add Verification Primitives
Add `requires()` and `ensures()` to your message handlers:
```typescript
bus.on("USER_LOGIN", (payload) => {
requires(state.user.loggedIn === false, "Must not be logged in")
state.user.loggedIn = true
ensures(state.user.loggedIn === true, "Must be logged in")
})
```
### 3. Validate Configuration
```bash
bun verify --validate
```
This checks for incomplete configuration and validates bounds.
### 4. Run Verification
```bash
bun verify
```
This generates TLA+ specs and runs the model checker.
## Configuration Guide
### State Field Types
The system handles different TypeScript types automatically:
**Boolean** - Auto-configured, no setup needed
```typescript
initialized: boolean
// → { type: 'boolean' }
```
**Enum** - Auto-configured from union types
```typescript
role: "admin" | "user" | "guest"
// → { type: "enum", values: ["admin", "user", "guest"] }
```
**Array** - Needs maximum length
```typescript
todos: Todo[]
// → { maxLength: 10 } // You configure this
```
**Number** - Needs range
```typescript
counter: number
// → { min: 0, max: 100 } // You configure this
```
**String** - Needs concrete values or abstract flag
```typescript
userId: string
// → { values: ["user1", "user2", "guest"] }
// OR
// → { abstract: true }
```
**Map/Set** - Needs maximum size
```typescript
cache: Map<string, Data>
// → { maxSize: 5 }
```
### Configuration Markers
Generated config uses special markers:
- `/* CONFIGURE */` - You must replace with a value
- `/* REVIEW */` - Auto-generated value, please verify
- `null` - Must be replaced with concrete value
### Example Configuration
```typescript
// specs/verification.config.ts
import { defineVerification } from '@fairfox/web-ext/verify'
export default defineVerification({
state: {
// Arrays
todos: { maxLength: 10 },
// Numbers
counter: { min: 0, max: 100 },
todoCount: { min: 0, max: 20 },
// Strings
"user.id": { values: ["alice", "bob", "guest"] },
"user.username": { abstract: true },
// Enums (auto-configured)
"user.role": { type: "enum", values: ["admin", "user", "guest"] },
"settings.theme": { type: "enum", values: ["light", "dark"] },
// Booleans (auto-configured)
"user.loggedIn": { type: "boolean" },
initialized: { type: "boolean" },
// Maps
cache: { maxSize: 5 },
},
messages: {
maxInFlight: 6,
maxTabs: 2,
},
onBuild: "warn",
onRelease: "error",
})
```
## Verification Primitives
Define correctness properties using high-level primitives:
```typescript
// specs/invariants.ts
import { before, requires, ensures, never, eventually } from '@fairfox/web-ext/verify'
export const invariants = [
// Temporal ordering
before("USER_LOGIN", "SYNC_TODOS"),
before("USER_LOGIN", ["SYNC_TODOS", "FETCH_DATA"]),
// State preconditions
requires("SYNC_TODOS", (state) => state.user.loggedIn === true),
requires("FETCH_DATA", (state) => state.initialized),
// State postconditions
ensures("USER_LOGIN", (state) => state.user.loggedIn === true),
ensures("CLEAR_TODOS", (state) => state.todos.length === 0),
// Concurrency control
never.concurrent(["SYNC_TODOS", "CLEAR_TODOS"]),
// Liveness
eventually.delivers("FETCH_DATA", { timeout: 5000 }),
// Escape hatch: raw TLA+
defineInvariant("TodoCountConsistent", {
description: "Todo count matches actual todos",
raw: `
\\A ctx1, ctx2 \\in Contexts :
(ports[ctx1] = "connected" /\\ ports[ctx2] = "connected") =>
todoCount[ctx1] = todoCount[ctx2]
`
}),
]
```
## CLI Commands
```bash
# Generate configuration
bun verify --setup
# Validate configuration
bun verify --validate
# Run verification
bun verify
# Show help
bun verify --help
```
## Configuration Validation
The validator detects common issues:
- **Incomplete configuration** - `/* CONFIGURE */` markers not replaced
- **Null placeholders** - `null` values that need concrete values
- **Invalid bounds** - min > max, negative lengths, etc.
- **Unrealistic bounds** - Values that will slow verification significantly
## Workflow
### Development
```typescript
// web-ext.config.ts
export default {
verification: {
enabled: true,
onBuild: "warn", // Show warnings, don't fail
onRelease: "error", // Block releases on violations
}
}
```
During development:
- Verification runs on build
- Shows warnings if violations found
- Doesn't block the build
### Release
During release builds:
- Verification runs with full bounds
- Blocks the build if violations found
- Ensures shipped code is verified
## Architecture
```
TypeScript Types
↓
[Extractor]
↓
Type Analysis
↓
[Config Generator]
↓
verification.config.ts ← User fills in
↓
[Validator]
↓
TLA+ Spec Generation
↓
[TLC Model Checker]
↓
Verification Results
```
## Current Status
**✅ Implemented:**
- Type extraction from TypeScript
- Config generation with smart comments
- Config validation
- CLI interface
**🚧 In Progress:**
- TLA+ spec generation from config
- Primitives → TLA+ translation
- TLC Docker integration
**📋 Planned:**
- Watch mode
- Interactive setup mode
- State mutation extraction from handlers
- Violation trace mapping back to TypeScript
## Contributing
This is part of the @fairfox/web-ext monorepo. See the main README for contribution guidelines.
## License
MIT