@fairfox/polly 0.1.2 → 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.
- package/{cli/polly.ts → dist/cli/polly.js} +100 -206
- package/dist/cli/polly.js.map +10 -0
- package/dist/scripts/build-extension.js +137 -0
- package/dist/scripts/build-extension.js.map +10 -0
- package/dist/vendor/verify/src/cli.js +2089 -0
- package/dist/vendor/verify/src/cli.js.map +16 -0
- package/dist/vendor/visualize/src/cli.js +2204 -0
- package/dist/vendor/visualize/src/cli.js.map +19 -0
- package/package.json +12 -12
- package/vendor/analysis/src/extract/adr.ts +0 -212
- package/vendor/analysis/src/extract/architecture.ts +0 -160
- package/vendor/analysis/src/extract/contexts.ts +0 -298
- package/vendor/analysis/src/extract/flows.ts +0 -309
- package/vendor/analysis/src/extract/handlers.ts +0 -321
- package/vendor/analysis/src/extract/index.ts +0 -9
- package/vendor/analysis/src/extract/integrations.ts +0 -329
- package/vendor/analysis/src/extract/manifest.ts +0 -298
- package/vendor/analysis/src/extract/types.ts +0 -389
- package/vendor/analysis/src/index.ts +0 -7
- package/vendor/analysis/src/types/adr.ts +0 -53
- package/vendor/analysis/src/types/architecture.ts +0 -245
- package/vendor/analysis/src/types/core.ts +0 -210
- package/vendor/analysis/src/types/index.ts +0 -18
- package/vendor/verify/src/adapters/base.ts +0 -164
- package/vendor/verify/src/adapters/detection.ts +0 -281
- package/vendor/verify/src/adapters/event-bus/index.ts +0 -480
- package/vendor/verify/src/adapters/web-extension/index.ts +0 -508
- package/vendor/verify/src/adapters/websocket/index.ts +0 -486
- package/vendor/verify/src/cli.ts +0 -430
- package/vendor/verify/src/codegen/config.ts +0 -354
- package/vendor/verify/src/codegen/tla.ts +0 -719
- package/vendor/verify/src/config/parser.ts +0 -303
- package/vendor/verify/src/config/types.ts +0 -113
- package/vendor/verify/src/core/model.ts +0 -267
- package/vendor/verify/src/core/primitives.ts +0 -106
- package/vendor/verify/src/extract/handlers.ts +0 -2
- package/vendor/verify/src/extract/types.ts +0 -2
- package/vendor/verify/src/index.ts +0 -150
- package/vendor/verify/src/primitives/index.ts +0 -102
- package/vendor/verify/src/runner/docker.ts +0 -283
- package/vendor/verify/src/types.ts +0 -51
- package/vendor/visualize/src/cli.ts +0 -365
- package/vendor/visualize/src/codegen/structurizr.ts +0 -770
- package/vendor/visualize/src/index.ts +0 -13
- package/vendor/visualize/src/runner/export.ts +0 -235
- package/vendor/visualize/src/viewer/server.ts +0 -485
- /package/dist/{background → src/background}/index.js +0 -0
- /package/dist/{background → src/background}/index.js.map +0 -0
- /package/dist/{background → src/background}/message-router.js +0 -0
- /package/dist/{background → src/background}/message-router.js.map +0 -0
- /package/dist/{index.js → src/index.js} +0 -0
- /package/dist/{index.js.map → src/index.js.map} +0 -0
- /package/dist/{shared → src/shared}/adapters/index.js +0 -0
- /package/dist/{shared → src/shared}/adapters/index.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/context-helpers.js +0 -0
- /package/dist/{shared → src/shared}/lib/context-helpers.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/errors.js +0 -0
- /package/dist/{shared → src/shared}/lib/errors.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/message-bus.js +0 -0
- /package/dist/{shared → src/shared}/lib/message-bus.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/state.js +0 -0
- /package/dist/{shared → src/shared}/lib/state.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/test-helpers.js +0 -0
- /package/dist/{shared → src/shared}/lib/test-helpers.js.map +0 -0
- /package/dist/{shared → src/shared}/state/app-state.js +0 -0
- /package/dist/{shared → src/shared}/state/app-state.js.map +0 -0
- /package/dist/{shared → src/shared}/types/messages.js +0 -0
- /package/dist/{shared → src/shared}/types/messages.js.map +0 -0
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
// ═══════════════════════════════════════════════════════════════
|
|
2
|
-
// Verification Primitives (Domain-Agnostic)
|
|
3
|
-
// ═══════════════════════════════════════════════════════════════
|
|
4
|
-
//
|
|
5
|
-
// These are runtime no-ops but extracted during verification.
|
|
6
|
-
// They work with ANY message-passing system, not just web extensions.
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Assert a precondition that must be true when the handler executes.
|
|
10
|
-
*
|
|
11
|
-
* In production: No-op (compiled away)
|
|
12
|
-
* In verification: Translated to TLA+ assertion
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* messageBus.on("USER_LOGIN", (payload) => {
|
|
16
|
-
* requires(state.user.loggedIn === false, "User must not be logged in")
|
|
17
|
-
* state.user.loggedIn = true
|
|
18
|
-
* })
|
|
19
|
-
*/
|
|
20
|
-
export function requires(condition: boolean, message?: string): void {
|
|
21
|
-
// Runtime no-op - only used during verification
|
|
22
|
-
if (!condition && message && process.env["NODE_ENV"] === "development") {
|
|
23
|
-
console.warn(`Precondition failed: ${message}`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Assert a postcondition that must be true after the handler completes.
|
|
29
|
-
*
|
|
30
|
-
* In production: No-op (compiled away)
|
|
31
|
-
* In verification: Translated to TLA+ assertion
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* messageBus.on("USER_LOGIN", (payload) => {
|
|
35
|
-
* state.user.loggedIn = true
|
|
36
|
-
* ensures(state.user.loggedIn === true, "User must be logged in")
|
|
37
|
-
* })
|
|
38
|
-
*/
|
|
39
|
-
export function ensures(condition: boolean, message?: string): void {
|
|
40
|
-
// Runtime no-op - only used during verification
|
|
41
|
-
if (!condition && message && process.env["NODE_ENV"] === "development") {
|
|
42
|
-
console.warn(`Postcondition failed: ${message}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Define a global invariant that must always hold.
|
|
48
|
-
*
|
|
49
|
-
* In production: No-op (compiled away)
|
|
50
|
-
* In verification: Translated to TLA+ invariant
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* invariant("UserIdConsistent", () =>
|
|
54
|
-
* state.user.loggedIn === false || state.user.id !== null
|
|
55
|
-
* )
|
|
56
|
-
*/
|
|
57
|
-
export function invariant(name: string, condition: () => boolean): void {
|
|
58
|
-
// Runtime no-op - only used during verification
|
|
59
|
-
if (!condition() && process.env["NODE_ENV"] === "development") {
|
|
60
|
-
console.warn(`Invariant ${name} violated`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Assert that a value is within a valid range.
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* requires(inRange(todoCount, 0, 100), "Todo count must be 0-100")
|
|
69
|
-
*/
|
|
70
|
-
export function inRange(value: number, min: number, max: number): boolean {
|
|
71
|
-
return value >= min && value <= max;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Assert that a value is one of the allowed values.
|
|
76
|
-
*
|
|
77
|
-
* @example
|
|
78
|
-
* requires(oneOf(state.user.role, ["admin", "user"]), "Role must be admin or user")
|
|
79
|
-
*/
|
|
80
|
-
export function oneOf<T>(value: T, allowed: T[]): boolean {
|
|
81
|
-
return allowed.includes(value);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Assert that an array has a specific length constraint.
|
|
86
|
-
*
|
|
87
|
-
* @example
|
|
88
|
-
* requires(hasLength(state.todos, { max: 10 }), "Too many todos")
|
|
89
|
-
*/
|
|
90
|
-
export function hasLength(array: unknown[], constraint: { min?: number; max?: number }): boolean {
|
|
91
|
-
if (constraint.min !== undefined && array.length < constraint.min) return false;
|
|
92
|
-
if (constraint.max !== undefined && array.length > constraint.max) return false;
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Re-export for convenience
|
|
97
|
-
export const verify = {
|
|
98
|
-
requires,
|
|
99
|
-
ensures,
|
|
100
|
-
invariant,
|
|
101
|
-
inRange,
|
|
102
|
-
oneOf,
|
|
103
|
-
hasLength,
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
export default verify;
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
// ═══════════════════════════════════════════════════════════════
|
|
2
|
-
// Public API for @fairfox/polly-verify
|
|
3
|
-
// ═══════════════════════════════════════════════════════════════
|
|
4
|
-
|
|
5
|
-
// ─────────────────────────────────────────────────────────────────
|
|
6
|
-
// Core Types (Domain-Agnostic)
|
|
7
|
-
// ─────────────────────────────────────────────────────────────────
|
|
8
|
-
export type {
|
|
9
|
-
CoreVerificationModel,
|
|
10
|
-
NodeDefinition,
|
|
11
|
-
MessageType,
|
|
12
|
-
RoutingPattern,
|
|
13
|
-
RoutingRule,
|
|
14
|
-
StateSchema,
|
|
15
|
-
FieldConfig,
|
|
16
|
-
StateAssignment,
|
|
17
|
-
VerificationCondition,
|
|
18
|
-
MessageHandler,
|
|
19
|
-
TypeKind,
|
|
20
|
-
TypeInfo,
|
|
21
|
-
Confidence,
|
|
22
|
-
FieldAnalysis,
|
|
23
|
-
CodebaseAnalysis,
|
|
24
|
-
} from "./core/model";
|
|
25
|
-
|
|
26
|
-
// ─────────────────────────────────────────────────────────────────
|
|
27
|
-
// Verification Primitives (Domain-Agnostic)
|
|
28
|
-
// ─────────────────────────────────────────────────────────────────
|
|
29
|
-
export {
|
|
30
|
-
requires,
|
|
31
|
-
ensures,
|
|
32
|
-
invariant,
|
|
33
|
-
inRange,
|
|
34
|
-
oneOf,
|
|
35
|
-
hasLength,
|
|
36
|
-
verify,
|
|
37
|
-
} from "./core/primitives";
|
|
38
|
-
|
|
39
|
-
// Also re-export from old location for backward compatibility
|
|
40
|
-
export * from "./primitives/index";
|
|
41
|
-
|
|
42
|
-
// ─────────────────────────────────────────────────────────────────
|
|
43
|
-
// Adapters
|
|
44
|
-
// ─────────────────────────────────────────────────────────────────
|
|
45
|
-
export type { RoutingAdapter, AdapterConfig } from "./adapters/base";
|
|
46
|
-
export { BaseRoutingAdapter } from "./adapters/base";
|
|
47
|
-
export {
|
|
48
|
-
WebExtensionAdapter,
|
|
49
|
-
type WebExtensionAdapterConfig,
|
|
50
|
-
type ExtensionContext,
|
|
51
|
-
} from "./adapters/web-extension";
|
|
52
|
-
export {
|
|
53
|
-
EventBusAdapter,
|
|
54
|
-
type EventBusAdapterConfig,
|
|
55
|
-
} from "./adapters/event-bus";
|
|
56
|
-
export {
|
|
57
|
-
WebSocketAdapter,
|
|
58
|
-
type WebSocketAdapterConfig,
|
|
59
|
-
} from "./adapters/websocket";
|
|
60
|
-
|
|
61
|
-
// Adapter Detection
|
|
62
|
-
export {
|
|
63
|
-
AdapterDetector,
|
|
64
|
-
detectAdapter,
|
|
65
|
-
type AdapterDetectionResult,
|
|
66
|
-
} from "./adapters/detection";
|
|
67
|
-
|
|
68
|
-
// ─────────────────────────────────────────────────────────────────
|
|
69
|
-
// Configuration
|
|
70
|
-
// ─────────────────────────────────────────────────────────────────
|
|
71
|
-
export type {
|
|
72
|
-
AdapterVerificationConfig,
|
|
73
|
-
LegacyVerificationConfig,
|
|
74
|
-
UnifiedVerificationConfig,
|
|
75
|
-
ConfigIssue,
|
|
76
|
-
ValidationResult as ConfigValidationResult,
|
|
77
|
-
} from "./config/types";
|
|
78
|
-
export { isAdapterConfig, isLegacyConfig } from "./config/types";
|
|
79
|
-
|
|
80
|
-
import type { UnifiedVerificationConfig } from "./config/types";
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Define verification configuration with type checking
|
|
84
|
-
*
|
|
85
|
-
* Supports both new adapter-based and legacy configurations.
|
|
86
|
-
*
|
|
87
|
-
* @example New adapter-based format:
|
|
88
|
-
* ```typescript
|
|
89
|
-
* import { WebExtensionAdapter, defineVerification } from '@fairfox/polly-verify'
|
|
90
|
-
*
|
|
91
|
-
* export default defineVerification({
|
|
92
|
-
* adapter: new WebExtensionAdapter({
|
|
93
|
-
* tsConfigPath: "./tsconfig.json",
|
|
94
|
-
* maxInFlight: 6,
|
|
95
|
-
* }),
|
|
96
|
-
* state: {
|
|
97
|
-
* "user.role": { type: "enum", values: ["admin", "user", "guest"] },
|
|
98
|
-
* },
|
|
99
|
-
* })
|
|
100
|
-
* ```
|
|
101
|
-
*
|
|
102
|
-
* @example Legacy format (backward compatible):
|
|
103
|
-
* ```typescript
|
|
104
|
-
* export default defineVerification({
|
|
105
|
-
* state: {
|
|
106
|
-
* "user.role": { type: "enum", values: ["admin", "user", "guest"] },
|
|
107
|
-
* },
|
|
108
|
-
* messages: {
|
|
109
|
-
* maxInFlight: 6,
|
|
110
|
-
* maxTabs: 2,
|
|
111
|
-
* },
|
|
112
|
-
* })
|
|
113
|
-
* ```
|
|
114
|
-
*/
|
|
115
|
-
export function defineVerification<T extends UnifiedVerificationConfig>(config: T): T {
|
|
116
|
-
// Validate configuration structure
|
|
117
|
-
if ("adapter" in config) {
|
|
118
|
-
// New adapter-based format
|
|
119
|
-
if (!config.adapter) {
|
|
120
|
-
throw new Error("Configuration must include an adapter");
|
|
121
|
-
}
|
|
122
|
-
if (!config.state) {
|
|
123
|
-
throw new Error("Configuration must include state bounds");
|
|
124
|
-
}
|
|
125
|
-
} else if ("messages" in config) {
|
|
126
|
-
// Legacy format
|
|
127
|
-
if (!config.state) {
|
|
128
|
-
throw new Error("Configuration must include state bounds");
|
|
129
|
-
}
|
|
130
|
-
if (!config.messages) {
|
|
131
|
-
throw new Error("Legacy configuration must include messages bounds");
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
throw new Error(
|
|
135
|
-
"Invalid configuration format. Must include either 'adapter' (new format) or 'messages' (legacy format)"
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return config;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ─────────────────────────────────────────────────────────────────
|
|
143
|
-
// Legacy API (Backward Compatibility)
|
|
144
|
-
// ─────────────────────────────────────────────────────────────────
|
|
145
|
-
export type { VerificationConfig, ValidationResult } from "./types";
|
|
146
|
-
export { HandlerExtractor, extractHandlers } from "./extract/handlers";
|
|
147
|
-
export { analyzeCodebase } from "./extract/types";
|
|
148
|
-
export { TLAGenerator, generateTLA } from "./codegen/tla";
|
|
149
|
-
export { generateConfig } from "./codegen/config";
|
|
150
|
-
export { validateConfig } from "./config/parser";
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
// Verification primitives for formal verification
|
|
2
|
-
// These are runtime no-ops but extracted during verification
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Assert a precondition that must be true when the handler executes.
|
|
6
|
-
*
|
|
7
|
-
* In production: No-op (compiled away)
|
|
8
|
-
* In verification: Translated to TLA+ assertion
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* messageBus.on("USER_LOGIN", (payload) => {
|
|
12
|
-
* requires(state.user.loggedIn === false, "User must not be logged in")
|
|
13
|
-
* state.user.loggedIn = true
|
|
14
|
-
* })
|
|
15
|
-
*/
|
|
16
|
-
export function requires(condition: boolean, message?: string): void {
|
|
17
|
-
// Runtime no-op - only used during verification
|
|
18
|
-
if (!condition && message && process.env.NODE_ENV === "development") {
|
|
19
|
-
console.warn(`Precondition failed: ${message}`);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Assert a postcondition that must be true after the handler completes.
|
|
25
|
-
*
|
|
26
|
-
* In production: No-op (compiled away)
|
|
27
|
-
* In verification: Translated to TLA+ assertion
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* messageBus.on("USER_LOGIN", (payload) => {
|
|
31
|
-
* state.user.loggedIn = true
|
|
32
|
-
* ensures(state.user.loggedIn === true, "User must be logged in")
|
|
33
|
-
* })
|
|
34
|
-
*/
|
|
35
|
-
export function ensures(condition: boolean, message?: string): void {
|
|
36
|
-
// Runtime no-op - only used during verification
|
|
37
|
-
if (!condition && message && process.env.NODE_ENV === "development") {
|
|
38
|
-
console.warn(`Postcondition failed: ${message}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Define a global invariant that must always hold.
|
|
44
|
-
*
|
|
45
|
-
* In production: No-op (compiled away)
|
|
46
|
-
* In verification: Translated to TLA+ invariant
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* invariant("UserIdConsistent", () =>
|
|
50
|
-
* state.user.loggedIn === false || state.user.id !== null
|
|
51
|
-
* )
|
|
52
|
-
*/
|
|
53
|
-
export function invariant(name: string, condition: () => boolean): void {
|
|
54
|
-
// Runtime no-op - only used during verification
|
|
55
|
-
if (!condition() && process.env.NODE_ENV === "development") {
|
|
56
|
-
console.warn(`Invariant ${name} violated`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Assert that a value is within a valid range.
|
|
62
|
-
*
|
|
63
|
-
* @example
|
|
64
|
-
* requires(inRange(todoCount, 0, 100), "Todo count must be 0-100")
|
|
65
|
-
*/
|
|
66
|
-
export function inRange(value: number, min: number, max: number): boolean {
|
|
67
|
-
return value >= min && value <= max;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Assert that a value is one of the allowed values.
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* requires(oneOf(state.user.role, ["admin", "user"]), "Role must be admin or user")
|
|
75
|
-
*/
|
|
76
|
-
export function oneOf<T>(value: T, allowed: T[]): boolean {
|
|
77
|
-
return allowed.includes(value);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Assert that an array has a specific length constraint.
|
|
82
|
-
*
|
|
83
|
-
* @example
|
|
84
|
-
* requires(hasLength(state.todos, { max: 10 }), "Too many todos")
|
|
85
|
-
*/
|
|
86
|
-
export function hasLength(array: unknown[], constraint: { min?: number; max?: number }): boolean {
|
|
87
|
-
if (constraint.min !== undefined && array.length < constraint.min) return false;
|
|
88
|
-
if (constraint.max !== undefined && array.length > constraint.max) return false;
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Re-export for convenience
|
|
93
|
-
export const verify = {
|
|
94
|
-
requires,
|
|
95
|
-
ensures,
|
|
96
|
-
invariant,
|
|
97
|
-
inRange,
|
|
98
|
-
oneOf,
|
|
99
|
-
hasLength,
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export default verify;
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
// Docker container management for TLA+ verification
|
|
2
|
-
|
|
3
|
-
import { spawn, type ChildProcess } from "node:child_process";
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import * as path from "node:path";
|
|
6
|
-
import * as os from "node:os";
|
|
7
|
-
|
|
8
|
-
export type DockerRunResult = {
|
|
9
|
-
exitCode: number;
|
|
10
|
-
stdout: string;
|
|
11
|
-
stderr: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export class DockerRunner {
|
|
15
|
-
private containerName = "web-ext-tla-verify";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Check if Docker is available
|
|
19
|
-
*/
|
|
20
|
-
async isDockerAvailable(): Promise<boolean> {
|
|
21
|
-
try {
|
|
22
|
-
const result = await this.runCommand("docker", ["--version"]);
|
|
23
|
-
return result.exitCode === 0;
|
|
24
|
-
} catch {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Check if TLA+ image exists
|
|
31
|
-
*/
|
|
32
|
-
async hasImage(): Promise<boolean> {
|
|
33
|
-
try {
|
|
34
|
-
const result = await this.runCommand("docker", ["images", "-q", "talex5/tla"]);
|
|
35
|
-
return result.stdout.trim().length > 0;
|
|
36
|
-
} catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Pull TLA+ image
|
|
43
|
-
*/
|
|
44
|
-
async pullImage(onProgress?: (line: string) => void): Promise<void> {
|
|
45
|
-
await this.runCommandStreaming("docker", ["pull", "talex5/tla:latest"], onProgress);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Run TLC model checker on a spec
|
|
50
|
-
*/
|
|
51
|
-
async runTLC(
|
|
52
|
-
specPath: string,
|
|
53
|
-
options?: {
|
|
54
|
-
workers?: number;
|
|
55
|
-
timeout?: number;
|
|
56
|
-
}
|
|
57
|
-
): Promise<TLCResult> {
|
|
58
|
-
// Ensure spec file exists
|
|
59
|
-
if (!fs.existsSync(specPath)) {
|
|
60
|
-
throw new Error(`Spec file not found: ${specPath}`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const specDir = path.dirname(specPath);
|
|
64
|
-
const specName = path.basename(specPath, ".tla");
|
|
65
|
-
const cfgPath = path.join(specDir, `${specName}.cfg`);
|
|
66
|
-
|
|
67
|
-
// Ensure cfg file exists
|
|
68
|
-
if (!fs.existsSync(cfgPath)) {
|
|
69
|
-
throw new Error(`Config file not found: ${cfgPath}`);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Run TLC in Docker
|
|
73
|
-
// Use sh -c to cd into /specs directory so TLC can find imported modules
|
|
74
|
-
const args = [
|
|
75
|
-
"run",
|
|
76
|
-
"--rm",
|
|
77
|
-
"-v",
|
|
78
|
-
`${specDir}:/specs`,
|
|
79
|
-
"talex5/tla",
|
|
80
|
-
"sh",
|
|
81
|
-
"-c",
|
|
82
|
-
`cd /specs && tlc -workers ${options?.workers || 1} ${specName}.tla`,
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
const result = await this.runCommand("docker", args, {
|
|
86
|
-
timeout: options?.timeout || 60000,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
return this.parseTLCOutput(result);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Parse TLC output
|
|
94
|
-
*/
|
|
95
|
-
private parseTLCOutput(result: DockerRunResult): TLCResult {
|
|
96
|
-
const output = result.stdout + result.stderr;
|
|
97
|
-
|
|
98
|
-
// Check for violations
|
|
99
|
-
const violationMatch = output.match(/Error: Invariant (.*?) is violated/);
|
|
100
|
-
if (violationMatch) {
|
|
101
|
-
return {
|
|
102
|
-
success: false,
|
|
103
|
-
violation: {
|
|
104
|
-
type: "invariant",
|
|
105
|
-
name: violationMatch[1],
|
|
106
|
-
trace: this.extractTrace(output),
|
|
107
|
-
},
|
|
108
|
-
output,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Check for errors
|
|
113
|
-
if (result.exitCode !== 0 || output.includes("Error:")) {
|
|
114
|
-
return {
|
|
115
|
-
success: false,
|
|
116
|
-
error: this.extractError(output),
|
|
117
|
-
output,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Success
|
|
122
|
-
const statesMatch = output.match(/(\d+) states generated/);
|
|
123
|
-
const distinctMatch = output.match(/(\d+) distinct states/);
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
success: true,
|
|
127
|
-
stats: {
|
|
128
|
-
statesGenerated: statesMatch ? Number.parseInt(statesMatch[1]) : 0,
|
|
129
|
-
distinctStates: distinctMatch ? Number.parseInt(distinctMatch[1]) : 0,
|
|
130
|
-
},
|
|
131
|
-
output,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Extract error trace from TLC output
|
|
137
|
-
*/
|
|
138
|
-
private extractTrace(output: string): string[] {
|
|
139
|
-
const lines = output.split("\n");
|
|
140
|
-
const trace: string[] = [];
|
|
141
|
-
let inTrace = false;
|
|
142
|
-
|
|
143
|
-
for (const line of lines) {
|
|
144
|
-
if (line.includes("State ") && line.includes(":")) {
|
|
145
|
-
inTrace = true;
|
|
146
|
-
trace.push(line);
|
|
147
|
-
} else if (inTrace) {
|
|
148
|
-
if (line.trim() === "" || line.startsWith("Error:")) {
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
trace.push(line);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return trace;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Extract error message from TLC output
|
|
160
|
-
*/
|
|
161
|
-
private extractError(output: string): string {
|
|
162
|
-
const errorMatch = output.match(/Error: (.*?)(?:\n|$)/);
|
|
163
|
-
if (errorMatch) {
|
|
164
|
-
return errorMatch[1];
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Look for common error patterns
|
|
168
|
-
if (output.includes("Parse Error")) {
|
|
169
|
-
return "TLA+ syntax error in specification";
|
|
170
|
-
}
|
|
171
|
-
if (output.includes("Semantic Error")) {
|
|
172
|
-
return "Semantic error in specification";
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return "Unknown error occurred during model checking";
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Run a command and return output
|
|
180
|
-
*/
|
|
181
|
-
private runCommand(
|
|
182
|
-
command: string,
|
|
183
|
-
args: string[],
|
|
184
|
-
options?: { timeout?: number }
|
|
185
|
-
): Promise<DockerRunResult> {
|
|
186
|
-
return new Promise((resolve, reject) => {
|
|
187
|
-
const proc = spawn(command, args);
|
|
188
|
-
|
|
189
|
-
let stdout = "";
|
|
190
|
-
let stderr = "";
|
|
191
|
-
|
|
192
|
-
proc.stdout.on("data", (data) => {
|
|
193
|
-
stdout += data.toString();
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
proc.stderr.on("data", (data) => {
|
|
197
|
-
stderr += data.toString();
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
const timeout = options?.timeout
|
|
201
|
-
? setTimeout(() => {
|
|
202
|
-
proc.kill();
|
|
203
|
-
reject(new Error(`Command timed out after ${options.timeout}ms`));
|
|
204
|
-
}, options.timeout)
|
|
205
|
-
: null;
|
|
206
|
-
|
|
207
|
-
proc.on("close", (exitCode) => {
|
|
208
|
-
if (timeout) clearTimeout(timeout);
|
|
209
|
-
|
|
210
|
-
resolve({
|
|
211
|
-
exitCode: exitCode || 0,
|
|
212
|
-
stdout,
|
|
213
|
-
stderr,
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
proc.on("error", (error) => {
|
|
218
|
-
if (timeout) clearTimeout(timeout);
|
|
219
|
-
reject(error);
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Run a command with streaming output
|
|
226
|
-
*/
|
|
227
|
-
private runCommandStreaming(
|
|
228
|
-
command: string,
|
|
229
|
-
args: string[],
|
|
230
|
-
onOutput?: (line: string) => void
|
|
231
|
-
): Promise<void> {
|
|
232
|
-
return new Promise((resolve, reject) => {
|
|
233
|
-
const proc = spawn(command, args);
|
|
234
|
-
|
|
235
|
-
proc.stdout.on("data", (data) => {
|
|
236
|
-
if (onOutput) {
|
|
237
|
-
const lines = data.toString().split("\n");
|
|
238
|
-
for (const line of lines) {
|
|
239
|
-
if (line.trim()) {
|
|
240
|
-
onOutput(line.trim());
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
proc.stderr.on("data", (data) => {
|
|
247
|
-
if (onOutput) {
|
|
248
|
-
const lines = data.toString().split("\n");
|
|
249
|
-
for (const line of lines) {
|
|
250
|
-
if (line.trim()) {
|
|
251
|
-
onOutput(line.trim());
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
proc.on("close", (exitCode) => {
|
|
258
|
-
if (exitCode === 0) {
|
|
259
|
-
resolve();
|
|
260
|
-
} else {
|
|
261
|
-
reject(new Error(`Command failed with exit code ${exitCode}`));
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
proc.on("error", reject);
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export type TLCResult = {
|
|
271
|
-
success: boolean;
|
|
272
|
-
violation?: {
|
|
273
|
-
type: "invariant" | "property" | "deadlock";
|
|
274
|
-
name?: string;
|
|
275
|
-
trace: string[];
|
|
276
|
-
};
|
|
277
|
-
error?: string;
|
|
278
|
-
stats?: {
|
|
279
|
-
statesGenerated: number;
|
|
280
|
-
distinctStates: number;
|
|
281
|
-
};
|
|
282
|
-
output: string;
|
|
283
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// Core types for verification system
|
|
2
|
-
|
|
3
|
-
// Re-export shared types from analysis package
|
|
4
|
-
export type {
|
|
5
|
-
Context,
|
|
6
|
-
TypeKind,
|
|
7
|
-
TypeInfo,
|
|
8
|
-
FieldAnalysis,
|
|
9
|
-
Confidence,
|
|
10
|
-
MessageHandler,
|
|
11
|
-
StateAssignment,
|
|
12
|
-
VerificationCondition,
|
|
13
|
-
CodebaseAnalysis,
|
|
14
|
-
} from "@fairfox/polly-analysis";
|
|
15
|
-
|
|
16
|
-
export type VerificationConfig = {
|
|
17
|
-
preset?: "quick" | "balanced" | "thorough";
|
|
18
|
-
state: StateConfig;
|
|
19
|
-
messages: MessageConfig;
|
|
20
|
-
onBuild: "warn" | "error" | "off";
|
|
21
|
-
onRelease: "warn" | "error" | "off";
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type StateConfig = Record<string, FieldConfig>;
|
|
25
|
-
|
|
26
|
-
export type FieldConfig =
|
|
27
|
-
| { maxLength: number | null }
|
|
28
|
-
| { min: number | null; max: number | null }
|
|
29
|
-
| { type: "enum"; values: string[] }
|
|
30
|
-
| { values: string[] | null; abstract?: boolean }
|
|
31
|
-
| { maxSize: number | null; valueType?: any }
|
|
32
|
-
| { abstract: boolean };
|
|
33
|
-
|
|
34
|
-
export type MessageConfig = {
|
|
35
|
-
maxInFlight: number | null;
|
|
36
|
-
maxTabs: number | null;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export type ConfigIssue = {
|
|
40
|
-
type: "incomplete" | "null_placeholder" | "unrealistic_bound" | "invalid_value";
|
|
41
|
-
severity: "error" | "warning";
|
|
42
|
-
field?: string;
|
|
43
|
-
location?: { line: number; column: number };
|
|
44
|
-
message: string;
|
|
45
|
-
suggestion: string;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export type ValidationResult = {
|
|
49
|
-
valid: boolean;
|
|
50
|
-
issues: ConfigIssue[];
|
|
51
|
-
};
|