@fairfox/polly 0.1.0 → 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.
- package/README.md +9 -9
- package/cli/polly.ts +9 -3
- package/package.json +2 -2
- package/vendor/analysis/src/extract/adr.ts +212 -0
- package/vendor/analysis/src/extract/architecture.ts +160 -0
- package/vendor/analysis/src/extract/contexts.ts +298 -0
- package/vendor/analysis/src/extract/flows.ts +309 -0
- package/vendor/analysis/src/extract/handlers.ts +321 -0
- package/vendor/analysis/src/extract/index.ts +9 -0
- package/vendor/analysis/src/extract/integrations.ts +329 -0
- package/vendor/analysis/src/extract/manifest.ts +298 -0
- package/vendor/analysis/src/extract/types.ts +389 -0
- package/vendor/analysis/src/index.ts +7 -0
- package/vendor/analysis/src/types/adr.ts +53 -0
- package/vendor/analysis/src/types/architecture.ts +245 -0
- package/vendor/analysis/src/types/core.ts +210 -0
- package/vendor/analysis/src/types/index.ts +18 -0
- package/vendor/verify/src/adapters/base.ts +164 -0
- package/vendor/verify/src/adapters/detection.ts +281 -0
- package/vendor/verify/src/adapters/event-bus/index.ts +480 -0
- package/vendor/verify/src/adapters/web-extension/index.ts +508 -0
- package/vendor/verify/src/adapters/websocket/index.ts +486 -0
- package/vendor/verify/src/cli.ts +430 -0
- package/vendor/verify/src/codegen/config.ts +354 -0
- package/vendor/verify/src/codegen/tla.ts +719 -0
- package/vendor/verify/src/config/parser.ts +303 -0
- package/vendor/verify/src/config/types.ts +113 -0
- package/vendor/verify/src/core/model.ts +267 -0
- package/vendor/verify/src/core/primitives.ts +106 -0
- package/vendor/verify/src/extract/handlers.ts +2 -0
- package/vendor/verify/src/extract/types.ts +2 -0
- package/vendor/verify/src/index.ts +150 -0
- package/vendor/verify/src/primitives/index.ts +102 -0
- package/vendor/verify/src/runner/docker.ts +283 -0
- package/vendor/verify/src/types.ts +51 -0
- package/vendor/visualize/src/cli.ts +365 -0
- package/vendor/visualize/src/codegen/structurizr.ts +770 -0
- package/vendor/visualize/src/index.ts +13 -0
- package/vendor/visualize/src/runner/export.ts +235 -0
- package/vendor/visualize/src/viewer/server.ts +485 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// Configuration validator - detects incomplete configuration
|
|
2
|
+
|
|
3
|
+
import type { ValidationResult, ConfigIssue, VerificationConfig } from "../types";
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
|
|
7
|
+
export class ConfigValidator {
|
|
8
|
+
private issues: ConfigIssue[] = [];
|
|
9
|
+
|
|
10
|
+
validate(configPath: string): ValidationResult {
|
|
11
|
+
this.issues = [];
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(configPath)) {
|
|
14
|
+
this.issues.push({
|
|
15
|
+
type: "incomplete",
|
|
16
|
+
severity: "error",
|
|
17
|
+
message: "Configuration file does not exist",
|
|
18
|
+
suggestion: "Run 'bun verify --setup' to generate configuration",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
valid: false,
|
|
23
|
+
issues: this.issues,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Read the source file
|
|
28
|
+
const configSource = fs.readFileSync(configPath, "utf-8");
|
|
29
|
+
|
|
30
|
+
// Check for CONFIGURE markers
|
|
31
|
+
this.checkConfigureMarkers(configSource);
|
|
32
|
+
|
|
33
|
+
// Check for REVIEW markers (warnings only)
|
|
34
|
+
this.checkReviewMarkers(configSource);
|
|
35
|
+
|
|
36
|
+
// Load and validate the actual config
|
|
37
|
+
try {
|
|
38
|
+
const config = this.loadConfig(configPath);
|
|
39
|
+
this.validateConfig(config);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
this.issues.push({
|
|
42
|
+
type: "invalid_value",
|
|
43
|
+
severity: "error",
|
|
44
|
+
message: `Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`,
|
|
45
|
+
suggestion: "Check for syntax errors in the configuration file",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const hasErrors = this.issues.some((i) => i.severity === "error");
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
valid: !hasErrors,
|
|
53
|
+
issues: this.issues,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private checkConfigureMarkers(source: string): void {
|
|
58
|
+
const configureRegex = /\/\*\s*CONFIGURE\s*\*\//g;
|
|
59
|
+
const matches = [...source.matchAll(configureRegex)];
|
|
60
|
+
|
|
61
|
+
if (matches.length > 0) {
|
|
62
|
+
// Find line numbers for each match
|
|
63
|
+
const lines = source.split("\n");
|
|
64
|
+
const locations: Array<{ line: number; column: number; context: string }> = [];
|
|
65
|
+
|
|
66
|
+
for (const match of matches) {
|
|
67
|
+
const position = match.index!;
|
|
68
|
+
const lineNumber = source.substring(0, position).split("\n").length;
|
|
69
|
+
const line = lines[lineNumber - 1];
|
|
70
|
+
|
|
71
|
+
// Extract field name from context
|
|
72
|
+
const fieldMatch = line.match(/"([^"]+)":\s*{/);
|
|
73
|
+
const fieldName = fieldMatch ? fieldMatch[1] : "unknown";
|
|
74
|
+
|
|
75
|
+
locations.push({
|
|
76
|
+
line: lineNumber,
|
|
77
|
+
column: match.index! - source.lastIndexOf("\n", position),
|
|
78
|
+
context: fieldName,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.issues.push({
|
|
83
|
+
type: "incomplete",
|
|
84
|
+
severity: "error",
|
|
85
|
+
message: `Found ${matches.length} incomplete configuration marker(s)`,
|
|
86
|
+
suggestion: "Replace all /* CONFIGURE */ markers with actual values",
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Add individual issues for each marker
|
|
90
|
+
for (const loc of locations) {
|
|
91
|
+
this.issues.push({
|
|
92
|
+
type: "incomplete",
|
|
93
|
+
severity: "error",
|
|
94
|
+
field: loc.context,
|
|
95
|
+
location: { line: loc.line, column: loc.column },
|
|
96
|
+
message: `Incomplete configuration at line ${loc.line}`,
|
|
97
|
+
suggestion: `Fill in value for "${loc.context}"`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private checkReviewMarkers(source: string): void {
|
|
104
|
+
const reviewRegex = /\/\*\s*REVIEW\s*\*\//g;
|
|
105
|
+
const matches = [...source.matchAll(reviewRegex)];
|
|
106
|
+
|
|
107
|
+
if (matches.length > 0) {
|
|
108
|
+
this.issues.push({
|
|
109
|
+
type: "incomplete",
|
|
110
|
+
severity: "warning",
|
|
111
|
+
message: `Found ${matches.length} value(s) that should be reviewed`,
|
|
112
|
+
suggestion: "Review auto-generated values marked with /* REVIEW */",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private loadConfig(configPath: string): VerificationConfig {
|
|
118
|
+
// Dynamic import of the config file
|
|
119
|
+
// Note: In production, this would use proper module loading
|
|
120
|
+
delete require.cache[require.resolve(path.resolve(configPath))];
|
|
121
|
+
const module = require(path.resolve(configPath));
|
|
122
|
+
return module.default || module;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private validateConfig(config: VerificationConfig): void {
|
|
126
|
+
// Check for null values
|
|
127
|
+
this.findNullPlaceholders(config.state, "state");
|
|
128
|
+
this.findNullPlaceholders(config.messages, "messages");
|
|
129
|
+
|
|
130
|
+
// Validate bounds
|
|
131
|
+
this.validateBounds(config);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private findNullPlaceholders(obj: any, path: string): void {
|
|
135
|
+
if (obj === null || obj === undefined) {
|
|
136
|
+
this.issues.push({
|
|
137
|
+
type: "null_placeholder",
|
|
138
|
+
severity: "error",
|
|
139
|
+
field: path,
|
|
140
|
+
message: `Configuration incomplete: ${path}`,
|
|
141
|
+
suggestion: "Replace null with a concrete value",
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (typeof obj !== "object") {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
151
|
+
const fullPath = `${path}.${key}`;
|
|
152
|
+
|
|
153
|
+
if (value === null) {
|
|
154
|
+
this.issues.push({
|
|
155
|
+
type: "null_placeholder",
|
|
156
|
+
severity: "error",
|
|
157
|
+
field: fullPath,
|
|
158
|
+
message: `Configuration incomplete: ${fullPath}`,
|
|
159
|
+
suggestion: "Replace null with a concrete value",
|
|
160
|
+
});
|
|
161
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
162
|
+
this.findNullPlaceholders(value, fullPath);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private validateBounds(config: VerificationConfig): void {
|
|
168
|
+
// Check messages config
|
|
169
|
+
if (config.messages.maxInFlight !== null) {
|
|
170
|
+
if (config.messages.maxInFlight < 1) {
|
|
171
|
+
this.issues.push({
|
|
172
|
+
type: "invalid_value",
|
|
173
|
+
severity: "error",
|
|
174
|
+
field: "messages.maxInFlight",
|
|
175
|
+
message: "maxInFlight must be at least 1",
|
|
176
|
+
suggestion: "Use a value between 4-10 for most cases",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (config.messages.maxInFlight > 20) {
|
|
181
|
+
this.issues.push({
|
|
182
|
+
type: "unrealistic_bound",
|
|
183
|
+
severity: "warning",
|
|
184
|
+
field: "messages.maxInFlight",
|
|
185
|
+
message: "Very high maxInFlight (>20) will slow verification significantly",
|
|
186
|
+
suggestion: "Use 4-10 for development, up to 20 for thorough verification",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (config.messages.maxTabs !== null) {
|
|
192
|
+
if (config.messages.maxTabs < 1) {
|
|
193
|
+
this.issues.push({
|
|
194
|
+
type: "invalid_value",
|
|
195
|
+
severity: "error",
|
|
196
|
+
field: "messages.maxTabs",
|
|
197
|
+
message: "maxTabs must be at least 1",
|
|
198
|
+
suggestion: "Use 2-3 for most cases",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (config.messages.maxTabs > 10) {
|
|
203
|
+
this.issues.push({
|
|
204
|
+
type: "unrealistic_bound",
|
|
205
|
+
severity: "warning",
|
|
206
|
+
field: "messages.maxTabs",
|
|
207
|
+
message: "Very high maxTabs (>10) will slow verification significantly",
|
|
208
|
+
suggestion: "Use 2-3 for most cases",
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Check state field bounds
|
|
214
|
+
for (const [fieldName, fieldConfig] of Object.entries(config.state)) {
|
|
215
|
+
if (typeof fieldConfig !== "object" || fieldConfig === null) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Array bounds
|
|
220
|
+
if ("maxLength" in fieldConfig) {
|
|
221
|
+
const maxLength = (fieldConfig as any).maxLength;
|
|
222
|
+
if (maxLength !== null) {
|
|
223
|
+
if (maxLength < 0) {
|
|
224
|
+
this.issues.push({
|
|
225
|
+
type: "invalid_value",
|
|
226
|
+
severity: "error",
|
|
227
|
+
field: `state.${fieldName}.maxLength`,
|
|
228
|
+
message: "maxLength cannot be negative",
|
|
229
|
+
suggestion: "Use a positive number",
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (maxLength > 50) {
|
|
234
|
+
this.issues.push({
|
|
235
|
+
type: "unrealistic_bound",
|
|
236
|
+
severity: "warning",
|
|
237
|
+
field: `state.${fieldName}.maxLength`,
|
|
238
|
+
message: `Very large maxLength (${maxLength}) will slow verification`,
|
|
239
|
+
suggestion: "Use 10-20 for most cases",
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Number bounds
|
|
246
|
+
if ("min" in fieldConfig && "max" in fieldConfig) {
|
|
247
|
+
const min = (fieldConfig as any).min;
|
|
248
|
+
const max = (fieldConfig as any).max;
|
|
249
|
+
|
|
250
|
+
if (min !== null && max !== null && min > max) {
|
|
251
|
+
this.issues.push({
|
|
252
|
+
type: "invalid_value",
|
|
253
|
+
severity: "error",
|
|
254
|
+
field: `state.${fieldName}`,
|
|
255
|
+
message: `Invalid range: min (${min}) > max (${max})`,
|
|
256
|
+
suggestion: "Ensure min is less than or equal to max",
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (min !== null && max !== null && max - min > 1000) {
|
|
261
|
+
this.issues.push({
|
|
262
|
+
type: "unrealistic_bound",
|
|
263
|
+
severity: "warning",
|
|
264
|
+
field: `state.${fieldName}`,
|
|
265
|
+
message: `Very large number range (${max - min}) will slow verification`,
|
|
266
|
+
suggestion: "Use smaller ranges when possible",
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Map/Set bounds
|
|
272
|
+
if ("maxSize" in fieldConfig) {
|
|
273
|
+
const maxSize = (fieldConfig as any).maxSize;
|
|
274
|
+
if (maxSize !== null) {
|
|
275
|
+
if (maxSize < 0) {
|
|
276
|
+
this.issues.push({
|
|
277
|
+
type: "invalid_value",
|
|
278
|
+
severity: "error",
|
|
279
|
+
field: `state.${fieldName}.maxSize`,
|
|
280
|
+
message: "maxSize cannot be negative",
|
|
281
|
+
suggestion: "Use a positive number",
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (maxSize > 20) {
|
|
286
|
+
this.issues.push({
|
|
287
|
+
type: "unrealistic_bound",
|
|
288
|
+
severity: "warning",
|
|
289
|
+
field: `state.${fieldName}.maxSize`,
|
|
290
|
+
message: `Very large maxSize (${maxSize}) will slow verification`,
|
|
291
|
+
suggestion: "Use 3-5 for most cases",
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function validateConfig(configPath: string): ValidationResult {
|
|
301
|
+
const validator = new ConfigValidator();
|
|
302
|
+
return validator.validate(configPath);
|
|
303
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// Configuration Types for Adapter-Based Verification
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
import type { RoutingAdapter } from "../adapters/base";
|
|
6
|
+
import type { StateSchema } from "../core/model";
|
|
7
|
+
|
|
8
|
+
// ─────────────────────────────────────────────────────────────────
|
|
9
|
+
// New Adapter-Based Configuration
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Adapter-based verification configuration (new format)
|
|
14
|
+
*/
|
|
15
|
+
export interface AdapterVerificationConfig {
|
|
16
|
+
/** The routing adapter to use */
|
|
17
|
+
adapter: RoutingAdapter;
|
|
18
|
+
|
|
19
|
+
/** State bounds (domain-agnostic) */
|
|
20
|
+
state: StateSchema;
|
|
21
|
+
|
|
22
|
+
/** Concurrency bounds */
|
|
23
|
+
bounds?: {
|
|
24
|
+
maxInFlight?: number;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Verification behavior on build */
|
|
29
|
+
onBuild?: "warn" | "error" | "off";
|
|
30
|
+
|
|
31
|
+
/** Verification behavior on release */
|
|
32
|
+
onRelease?: "warn" | "error" | "off";
|
|
33
|
+
|
|
34
|
+
/** Optional: Custom invariants */
|
|
35
|
+
invariants?: Array<{
|
|
36
|
+
name: string;
|
|
37
|
+
expression: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─────────────────────────────────────────────────────────────────
|
|
43
|
+
// Legacy Configuration (Backward Compatibility)
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Legacy verification configuration (backward compatible)
|
|
48
|
+
*/
|
|
49
|
+
export interface LegacyVerificationConfig {
|
|
50
|
+
/** State configuration (old format) */
|
|
51
|
+
state: Record<string, any>;
|
|
52
|
+
|
|
53
|
+
/** Message configuration (old format) */
|
|
54
|
+
messages: {
|
|
55
|
+
maxInFlight: number | null;
|
|
56
|
+
maxTabs: number | null;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Verification behavior on build */
|
|
60
|
+
onBuild: "warn" | "error" | "off";
|
|
61
|
+
|
|
62
|
+
/** Verification behavior on release */
|
|
63
|
+
onRelease: "warn" | "error" | "off";
|
|
64
|
+
|
|
65
|
+
/** Optional preset */
|
|
66
|
+
preset?: "quick" | "balanced" | "thorough";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─────────────────────────────────────────────────────────────────
|
|
70
|
+
// Unified Configuration Type
|
|
71
|
+
// ─────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Union of new and legacy configuration formats
|
|
75
|
+
*/
|
|
76
|
+
export type UnifiedVerificationConfig = AdapterVerificationConfig | LegacyVerificationConfig;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Type guard to check if config is adapter-based (new format)
|
|
80
|
+
*/
|
|
81
|
+
export function isAdapterConfig(
|
|
82
|
+
config: UnifiedVerificationConfig
|
|
83
|
+
): config is AdapterVerificationConfig {
|
|
84
|
+
return "adapter" in config;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Type guard to check if config is legacy format
|
|
89
|
+
*/
|
|
90
|
+
export function isLegacyConfig(
|
|
91
|
+
config: UnifiedVerificationConfig
|
|
92
|
+
): config is LegacyVerificationConfig {
|
|
93
|
+
return "messages" in config && !("adapter" in config);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─────────────────────────────────────────────────────────────────
|
|
97
|
+
// Configuration Validation
|
|
98
|
+
// ─────────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
export interface ConfigIssue {
|
|
101
|
+
type: "incomplete" | "null_placeholder" | "unrealistic_bound" | "invalid_value";
|
|
102
|
+
severity: "error" | "warning";
|
|
103
|
+
field?: string;
|
|
104
|
+
location?: { line: number; column: number };
|
|
105
|
+
message: string;
|
|
106
|
+
suggestion: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ValidationResult {
|
|
110
|
+
valid: boolean;
|
|
111
|
+
issues: ConfigIssue[];
|
|
112
|
+
configType?: "adapter" | "legacy";
|
|
113
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// Core Verification Model (Domain-Agnostic)
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// This module defines abstract types for any message-passing system.
|
|
6
|
+
// These types are independent of the specific domain (web extensions,
|
|
7
|
+
// actors, event buses, etc.).
|
|
8
|
+
//
|
|
9
|
+
// Adapters translate domain-specific code into this universal model.
|
|
10
|
+
|
|
11
|
+
// ─────────────────────────────────────────────────────────────────
|
|
12
|
+
// Type System (Universal)
|
|
13
|
+
// ─────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export type TypeKind =
|
|
16
|
+
| "boolean"
|
|
17
|
+
| "string"
|
|
18
|
+
| "number"
|
|
19
|
+
| "enum"
|
|
20
|
+
| "array"
|
|
21
|
+
| "object"
|
|
22
|
+
| "map"
|
|
23
|
+
| "set"
|
|
24
|
+
| "union"
|
|
25
|
+
| "null"
|
|
26
|
+
| "unknown";
|
|
27
|
+
|
|
28
|
+
export type TypeInfo = {
|
|
29
|
+
name: string;
|
|
30
|
+
kind: TypeKind;
|
|
31
|
+
nullable: boolean;
|
|
32
|
+
elementType?: TypeInfo; // For arrays, sets
|
|
33
|
+
valueType?: TypeInfo; // For maps
|
|
34
|
+
properties?: Record<string, TypeInfo>; // For objects
|
|
35
|
+
enumValues?: string[]; // For enums
|
|
36
|
+
unionTypes?: TypeInfo[]; // For unions
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// ─────────────────────────────────────────────────────────────────
|
|
40
|
+
// Node System (Abstract)
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A node represents an entity in the system that can send/receive messages.
|
|
45
|
+
*
|
|
46
|
+
* Examples:
|
|
47
|
+
* - Web extension: "background", "content", "popup"
|
|
48
|
+
* - Actor system: Individual actor instances
|
|
49
|
+
* - Event bus: Emitters/listeners
|
|
50
|
+
* - Worker threads: Main thread + worker instances
|
|
51
|
+
*/
|
|
52
|
+
export type NodeDefinition = {
|
|
53
|
+
/** Unique identifier for this node */
|
|
54
|
+
id: string;
|
|
55
|
+
|
|
56
|
+
/** Type of node (adapter-specific) */
|
|
57
|
+
type: string;
|
|
58
|
+
|
|
59
|
+
/** Which nodes can this send messages to? */
|
|
60
|
+
canSendTo: string[];
|
|
61
|
+
|
|
62
|
+
/** Which nodes can send messages to this? */
|
|
63
|
+
canReceiveFrom: string[];
|
|
64
|
+
|
|
65
|
+
/** Optional: Additional metadata */
|
|
66
|
+
metadata?: Record<string, unknown>;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// ─────────────────────────────────────────────────────────────────
|
|
70
|
+
// Message Types (Abstract)
|
|
71
|
+
// ─────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Defines a type of message that flows through the system
|
|
75
|
+
*/
|
|
76
|
+
export type MessageType = {
|
|
77
|
+
/** Name/identifier of the message type */
|
|
78
|
+
name: string;
|
|
79
|
+
|
|
80
|
+
/** Schema of the message payload */
|
|
81
|
+
payload: TypeInfo;
|
|
82
|
+
|
|
83
|
+
/** Routing constraints */
|
|
84
|
+
routing: {
|
|
85
|
+
/** Which node types can send this message? */
|
|
86
|
+
from: string[];
|
|
87
|
+
|
|
88
|
+
/** Which node types can receive this message? */
|
|
89
|
+
to: string[];
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** Optional: Expected response type */
|
|
93
|
+
response?: TypeInfo;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// ─────────────────────────────────────────────────────────────────
|
|
97
|
+
// Routing Rules (Abstract)
|
|
98
|
+
// ─────────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
export type RoutingPattern =
|
|
101
|
+
| "direct" // Point-to-point (actor.send)
|
|
102
|
+
| "broadcast" // One-to-many (event bus)
|
|
103
|
+
| "request-reply" // Request-response (extension messaging)
|
|
104
|
+
| "publish-subscribe" // Pub/sub pattern
|
|
105
|
+
| "custom"; // Adapter-defined
|
|
106
|
+
|
|
107
|
+
export type RoutingRule = {
|
|
108
|
+
/** Type of routing pattern */
|
|
109
|
+
pattern: RoutingPattern;
|
|
110
|
+
|
|
111
|
+
/** Which message types use this routing? */
|
|
112
|
+
messageTypes: string[];
|
|
113
|
+
|
|
114
|
+
/** Optional: Custom routing logic description */
|
|
115
|
+
description?: string;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────
|
|
119
|
+
// State Schema (Abstract)
|
|
120
|
+
// ─────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Configuration for a state field
|
|
124
|
+
*/
|
|
125
|
+
export type FieldConfig =
|
|
126
|
+
| { maxLength: number | null }
|
|
127
|
+
| { min: number | null; max: number | null }
|
|
128
|
+
| { type: "enum"; values: string[] }
|
|
129
|
+
| { values: string[] | null; abstract?: boolean }
|
|
130
|
+
| { maxSize: number | null; valueType?: unknown }
|
|
131
|
+
| { abstract: boolean };
|
|
132
|
+
|
|
133
|
+
export type StateSchema = Record<string, FieldConfig>;
|
|
134
|
+
|
|
135
|
+
// ─────────────────────────────────────────────────────────────────
|
|
136
|
+
// State Mutations (Abstract)
|
|
137
|
+
// ─────────────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Represents an assignment to a state field
|
|
141
|
+
*/
|
|
142
|
+
export type StateAssignment = {
|
|
143
|
+
/** Field path (e.g., "user.loggedIn") */
|
|
144
|
+
field: string;
|
|
145
|
+
|
|
146
|
+
/** The assigned value */
|
|
147
|
+
value: string | boolean | number | null;
|
|
148
|
+
|
|
149
|
+
/** Optional condition guard */
|
|
150
|
+
conditional?: string;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// ─────────────────────────────────────────────────────────────────
|
|
154
|
+
// Verification Conditions (Abstract)
|
|
155
|
+
// ─────────────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A verification condition (precondition or postcondition)
|
|
159
|
+
*/
|
|
160
|
+
export type VerificationCondition = {
|
|
161
|
+
/** The condition expression as a string */
|
|
162
|
+
expression: string;
|
|
163
|
+
|
|
164
|
+
/** Optional error message */
|
|
165
|
+
message?: string;
|
|
166
|
+
|
|
167
|
+
/** Source location */
|
|
168
|
+
location: {
|
|
169
|
+
line: number;
|
|
170
|
+
column: number;
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// ─────────────────────────────────────────────────────────────────
|
|
175
|
+
// Message Handler (Abstract)
|
|
176
|
+
// ─────────────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Represents a message handler extracted from code
|
|
180
|
+
*/
|
|
181
|
+
export type MessageHandler = {
|
|
182
|
+
/** Which message type does this handle? */
|
|
183
|
+
messageType: string;
|
|
184
|
+
|
|
185
|
+
/** Which node handles this message? */
|
|
186
|
+
node: string;
|
|
187
|
+
|
|
188
|
+
/** State assignments made by this handler */
|
|
189
|
+
assignments: StateAssignment[];
|
|
190
|
+
|
|
191
|
+
/** Preconditions (requires() calls) */
|
|
192
|
+
preconditions: VerificationCondition[];
|
|
193
|
+
|
|
194
|
+
/** Postconditions (ensures() calls) */
|
|
195
|
+
postconditions: VerificationCondition[];
|
|
196
|
+
|
|
197
|
+
/** Source location */
|
|
198
|
+
location: {
|
|
199
|
+
file: string;
|
|
200
|
+
line: number;
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// ─────────────────────────────────────────────────────────────────
|
|
205
|
+
// Complete Verification Model (Abstract)
|
|
206
|
+
// ─────────────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The complete abstract model that the core verification engine operates on.
|
|
210
|
+
* This is what adapters produce from domain-specific code.
|
|
211
|
+
*/
|
|
212
|
+
export type CoreVerificationModel = {
|
|
213
|
+
/** All nodes in the system */
|
|
214
|
+
nodes: NodeDefinition[];
|
|
215
|
+
|
|
216
|
+
/** All message types that flow between nodes */
|
|
217
|
+
messageTypes: MessageType[];
|
|
218
|
+
|
|
219
|
+
/** Routing rules that govern message delivery */
|
|
220
|
+
routingRules: RoutingRule[];
|
|
221
|
+
|
|
222
|
+
/** State schema with bounds */
|
|
223
|
+
state: StateSchema;
|
|
224
|
+
|
|
225
|
+
/** All message handlers extracted from code */
|
|
226
|
+
handlers: MessageHandler[];
|
|
227
|
+
|
|
228
|
+
/** Concurrency bounds for model checking */
|
|
229
|
+
bounds: {
|
|
230
|
+
/** Maximum concurrent messages in flight */
|
|
231
|
+
maxConcurrentMessages: number;
|
|
232
|
+
|
|
233
|
+
/** Maximum number of nodes (for bounded model checking) */
|
|
234
|
+
maxNodes: number;
|
|
235
|
+
|
|
236
|
+
/** Optional: Adapter-specific bounds */
|
|
237
|
+
custom?: Record<string, number>;
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// ─────────────────────────────────────────────────────────────────
|
|
242
|
+
// Confidence Levels (Universal)
|
|
243
|
+
// ─────────────────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
export type Confidence = "high" | "medium" | "low";
|
|
246
|
+
|
|
247
|
+
export type FieldAnalysis = {
|
|
248
|
+
path: string;
|
|
249
|
+
type: TypeInfo;
|
|
250
|
+
confidence: Confidence;
|
|
251
|
+
evidence: string[];
|
|
252
|
+
suggestions: string[];
|
|
253
|
+
bounds?: {
|
|
254
|
+
min?: number;
|
|
255
|
+
max?: number;
|
|
256
|
+
maxLength?: number;
|
|
257
|
+
maxSize?: number;
|
|
258
|
+
values?: string[];
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export type CodebaseAnalysis = {
|
|
263
|
+
stateType: TypeInfo | null;
|
|
264
|
+
messageTypes: string[];
|
|
265
|
+
fields: FieldAnalysis[];
|
|
266
|
+
handlers: MessageHandler[];
|
|
267
|
+
};
|