speclogician 0.0.0b1__py3-none-any.whl → 0.0.0.dev1__py3-none-any.whl
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.
- speclogician/agent/funcs.py +29 -0
- speclogician/cmd/agent_cmd.py +89 -0
- speclogician/cmd/data_cmd.py +24 -0
- speclogician/cmd/model_cmd.py +42 -0
- speclogician/cmd/overlay_cmd.py +30 -0
- speclogician/cmd/scenario_cmd.py +61 -0
- speclogician/cmd/state_cmd.py +52 -0
- speclogician/data/artifact.py +8 -50
- speclogician/data/container.py +18 -384
- speclogician/data/mapping.py +18 -17
- speclogician/data/refs.py +12 -11
- speclogician/data/reports.py +11 -0
- speclogician/data/traces.py +15 -6
- speclogician/llms/llmtools.py +102 -0
- speclogician/llms/overlay.py +264 -0
- speclogician/main.py +36 -102
- speclogician/modeling/__init__.py +0 -31
- speclogician/modeling/component.py +4 -60
- speclogician/modeling/conflict.py +5 -19
- speclogician/modeling/domain.py +93 -280
- speclogician/modeling/model.py +206 -0
- speclogician/modeling/predicates.py +20 -22
- speclogician/modeling/report.py +33 -0
- speclogician/modeling/scenario.py +119 -87
- speclogician/sl_cmd.py +76 -0
- speclogician/state/change.py +98 -378
- speclogician/state/state.py +183 -399
- speclogician/tui/box.tcss +10 -0
- speclogician/tui/tui.py +131 -0
- speclogician/utils/__init__.py +1 -70
- speclogician/utils/imx.py +195 -0
- speclogician/utils/load.py +25 -147
- speclogician/utils/prompt.md +1 -325
- speclogician-0.0.0.dev1.dist-info/METADATA +21 -0
- speclogician-0.0.0.dev1.dist-info/RECORD +43 -0
- speclogician/commands/__init__.py +0 -15
- speclogician/commands/cmd_ch.py +0 -616
- speclogician/commands/cmd_find.py +0 -256
- speclogician/commands/cmd_view.py +0 -202
- speclogician/commands/runner.py +0 -149
- speclogician/commands/utils.py +0 -101
- speclogician/demos/.DS_Store +0 -0
- speclogician/demos/cmd_demo.py +0 -278
- speclogician/demos/loader.py +0 -135
- speclogician/demos/model.py +0 -27
- speclogician/demos/runner.py +0 -51
- speclogician/logic/__init__.py +0 -11
- speclogician/logic/api/__init__.py +0 -29
- speclogician/logic/api/client.py +0 -606
- speclogician/logic/api/decomp.py +0 -67
- speclogician/logic/api/scenario.py +0 -102
- speclogician/logic/api/traces.py +0 -59
- speclogician/logic/lib/__init__.py +0 -19
- speclogician/logic/lib/complement.py +0 -107
- speclogician/logic/lib/domain_model.py +0 -59
- speclogician/logic/lib/predicates.py +0 -151
- speclogician/logic/lib/scenarios.py +0 -369
- speclogician/logic/lib/traces.py +0 -114
- speclogician/logic/lib/transitions.py +0 -104
- speclogician/logic/main.py +0 -246
- speclogician/logic/strings.py +0 -194
- speclogician/logic/utils.py +0 -135
- speclogician/modeling/complement.py +0 -104
- speclogician/modeling/spec.py +0 -306
- speclogician/modeling/spec_stats.py +0 -39
- speclogician/presentation/api.py +0 -244
- speclogician/presentation/builders/_links.py +0 -44
- speclogician/presentation/builders/container.py +0 -53
- speclogician/presentation/builders/data_artifact.py +0 -42
- speclogician/presentation/builders/domain.py +0 -54
- speclogician/presentation/builders/instances_list.py +0 -38
- speclogician/presentation/builders/predicate.py +0 -51
- speclogician/presentation/builders/recommendations.py +0 -41
- speclogician/presentation/builders/scenario.py +0 -41
- speclogician/presentation/builders/scenario_complement.py +0 -82
- speclogician/presentation/builders/smart_find.py +0 -39
- speclogician/presentation/builders/spec.py +0 -39
- speclogician/presentation/builders/state_diff.py +0 -150
- speclogician/presentation/builders/state_instance.py +0 -42
- speclogician/presentation/builders/state_instance_summary.py +0 -84
- speclogician/presentation/builders/trace.py +0 -58
- speclogician/presentation/ctx.py +0 -38
- speclogician/presentation/models/container.py +0 -44
- speclogician/presentation/models/data_artifact.py +0 -33
- speclogician/presentation/models/domain.py +0 -50
- speclogician/presentation/models/instances_list.py +0 -23
- speclogician/presentation/models/predicate.py +0 -60
- speclogician/presentation/models/recommendations.py +0 -34
- speclogician/presentation/models/scenario.py +0 -31
- speclogician/presentation/models/scenario_complement.py +0 -40
- speclogician/presentation/models/smart_find.py +0 -34
- speclogician/presentation/models/spec.py +0 -32
- speclogician/presentation/models/state_diff.py +0 -34
- speclogician/presentation/models/state_instance.py +0 -31
- speclogician/presentation/models/state_instance_summary.py +0 -102
- speclogician/presentation/models/trace.py +0 -42
- speclogician/presentation/preview/__init__.py +0 -13
- speclogician/presentation/preview/cli.py +0 -50
- speclogician/presentation/preview/fixtures/__init__.py +0 -205
- speclogician/presentation/preview/fixtures/artifact_container.py +0 -150
- speclogician/presentation/preview/fixtures/data_artifact.py +0 -144
- speclogician/presentation/preview/fixtures/domain_model.py +0 -162
- speclogician/presentation/preview/fixtures/instances_list.py +0 -162
- speclogician/presentation/preview/fixtures/predicate.py +0 -184
- speclogician/presentation/preview/fixtures/scenario.py +0 -84
- speclogician/presentation/preview/fixtures/scenario_complement.py +0 -81
- speclogician/presentation/preview/fixtures/smart_find.py +0 -140
- speclogician/presentation/preview/fixtures/spec.py +0 -95
- speclogician/presentation/preview/fixtures/state_diff.py +0 -158
- speclogician/presentation/preview/fixtures/state_instance.py +0 -128
- speclogician/presentation/preview/fixtures/state_instance_summary.py +0 -80
- speclogician/presentation/preview/fixtures/trace.py +0 -206
- speclogician/presentation/preview/registry.py +0 -42
- speclogician/presentation/renderers/__init__.py +0 -24
- speclogician/presentation/renderers/container.py +0 -136
- speclogician/presentation/renderers/data_artifact.py +0 -144
- speclogician/presentation/renderers/domain.py +0 -123
- speclogician/presentation/renderers/instances_list.py +0 -120
- speclogician/presentation/renderers/predicate.py +0 -180
- speclogician/presentation/renderers/recommendations.py +0 -90
- speclogician/presentation/renderers/scenario.py +0 -94
- speclogician/presentation/renderers/scenario_complement.py +0 -59
- speclogician/presentation/renderers/smart_find.py +0 -307
- speclogician/presentation/renderers/spec.py +0 -105
- speclogician/presentation/renderers/state_diff.py +0 -102
- speclogician/presentation/renderers/state_instance.py +0 -82
- speclogician/presentation/renderers/state_instance_summary.py +0 -143
- speclogician/presentation/renderers/trace.py +0 -122
- speclogician/shell/app.py +0 -170
- speclogician/shell/shell_ch.py +0 -263
- speclogician/shell/shell_view.py +0 -153
- speclogician/state/change_result.py +0 -32
- speclogician/state/diff.py +0 -191
- speclogician/state/inst.py +0 -574
- speclogician/state/recommendation.py +0 -13
- speclogician/state/recommender.py +0 -577
- speclogician/state/state_stats.py +0 -133
- speclogician/tui/__init__.py +0 -0
- speclogician/tui/app.py +0 -257
- speclogician/tui/app.tcss +0 -160
- speclogician/tui/demo.py +0 -45
- speclogician/tui/images/speclogician-full.png +0 -0
- speclogician/tui/images/speclogician-minimal.png +0 -0
- speclogician/tui/main_screen.py +0 -454
- speclogician/tui/splash_screen.py +0 -51
- speclogician/tui/stats_screen.py +0 -125
- speclogician/utils/testing.py +0 -151
- speclogician-0.0.0b1.dist-info/METADATA +0 -116
- speclogician-0.0.0b1.dist-info/RECORD +0 -139
- /speclogician/{presentation → agent}/__init__.py +0 -0
- /speclogician/{presentation/builders → cmd}/__init__.py +0 -0
- /speclogician/{presentation/models → llms}/__init__.py +0 -0
- {speclogician-0.0.0b1.dist-info → speclogician-0.0.0.dev1.dist-info}/WHEEL +0 -0
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Imandra Inc.
|
|
3
|
-
#
|
|
4
|
-
# speclogician/logic/lib/scenarios.py
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import asyncio
|
|
10
|
-
from typing import Iterable
|
|
11
|
-
|
|
12
|
-
from speclogician.logic.api import SpecLogicianImandraX
|
|
13
|
-
from speclogician.modeling.scenario import Scenario, Valid, Inconsistent, Missing
|
|
14
|
-
from speclogician.modeling.conflict import (
|
|
15
|
-
ScenarioConflict,
|
|
16
|
-
ScenarioOverlap,
|
|
17
|
-
ScenarioConsumed,
|
|
18
|
-
)
|
|
19
|
-
from speclogician.modeling.spec import Spec
|
|
20
|
-
from speclogician.utils import console
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def _select_scenarios(
|
|
24
|
-
spec: Spec,
|
|
25
|
-
*,
|
|
26
|
-
names: list[str],
|
|
27
|
-
predicates: list[str],
|
|
28
|
-
transitions: list[str]
|
|
29
|
-
) -> list[Scenario]:
|
|
30
|
-
name_set = set(names) if names else None
|
|
31
|
-
pred_set = set(predicates) if predicates else None
|
|
32
|
-
trans_set = set(transitions) if transitions else None
|
|
33
|
-
|
|
34
|
-
def _name_ok(sc: Scenario) -> bool:
|
|
35
|
-
return (name_set is None) or (sc.name in name_set)
|
|
36
|
-
|
|
37
|
-
def _contains_any_pred(sc: Scenario) -> bool:
|
|
38
|
-
if pred_set is None:
|
|
39
|
-
return True
|
|
40
|
-
sc_preds = set(list(sc.given) + list(sc.when))
|
|
41
|
-
return any(p in sc_preds for p in pred_set)
|
|
42
|
-
|
|
43
|
-
def _contains_any_trans(sc: Scenario) -> bool:
|
|
44
|
-
if trans_set is None:
|
|
45
|
-
return True
|
|
46
|
-
sc_trans = set(list(sc.then))
|
|
47
|
-
return any(t in sc_trans for t in trans_set)
|
|
48
|
-
|
|
49
|
-
return [
|
|
50
|
-
sc
|
|
51
|
-
for sc in spec.scenarios
|
|
52
|
-
if _name_ok(sc) and _contains_any_pred(sc) and _contains_any_trans(sc)
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _is_checkable(sc: Scenario) -> bool:
|
|
57
|
-
# must not be missing AND must be solver-consistent
|
|
58
|
-
return (not isinstance(sc.component_status, Missing)) and bool(sc.all_preds_consistent)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
async def _analyze_one_scenario(
|
|
62
|
-
ix: SpecLogicianImandraX,
|
|
63
|
-
*,
|
|
64
|
-
spec: Spec,
|
|
65
|
-
sc: Scenario
|
|
66
|
-
) -> None:
|
|
67
|
-
dm = spec.domain_model
|
|
68
|
-
|
|
69
|
-
# reset per-run fields
|
|
70
|
-
sc.preds_missing = []
|
|
71
|
-
sc.trans_missing = []
|
|
72
|
-
sc.given_preds_consistent = True
|
|
73
|
-
sc.when_preds_consistent = True
|
|
74
|
-
sc.all_preds_consistent = True
|
|
75
|
-
sc.component_status = Valid()
|
|
76
|
-
|
|
77
|
-
# Missing components
|
|
78
|
-
missing_preds: list[str] = []
|
|
79
|
-
for p in list(sc.given) + list(sc.when):
|
|
80
|
-
if dm.get_pred_by_name(p) is None:
|
|
81
|
-
missing_preds.append(p)
|
|
82
|
-
|
|
83
|
-
missing_trans: list[str] = []
|
|
84
|
-
for t in list(sc.then):
|
|
85
|
-
if dm.get_trans_by_name(t) is None:
|
|
86
|
-
missing_trans.append(t)
|
|
87
|
-
|
|
88
|
-
sc.preds_missing = missing_preds
|
|
89
|
-
sc.trans_missing = missing_trans
|
|
90
|
-
|
|
91
|
-
if missing_preds or missing_trans:
|
|
92
|
-
sc.component_status = Missing(
|
|
93
|
-
missing_preds=missing_preds,
|
|
94
|
-
missing_trans=missing_trans,
|
|
95
|
-
)
|
|
96
|
-
sc.given_preds_consistent = False if missing_preds else True
|
|
97
|
-
sc.when_preds_consistent = False if missing_preds else True
|
|
98
|
-
sc.all_preds_consistent = False
|
|
99
|
-
return
|
|
100
|
-
|
|
101
|
-
# local validation (raises on wrong kind / missing)
|
|
102
|
-
dm.assert_has_predicates(given=sc.given, when=sc.when)
|
|
103
|
-
dm.assert_has_transitions(then=sc.then)
|
|
104
|
-
|
|
105
|
-
try:
|
|
106
|
-
if list(sc.given):
|
|
107
|
-
r_given = await ix.check_scenario_consistent(
|
|
108
|
-
domain_model=dm,
|
|
109
|
-
scenario=sc,
|
|
110
|
-
check_state_preds=True,
|
|
111
|
-
check_action_preds=False,
|
|
112
|
-
)
|
|
113
|
-
sc.given_preds_consistent = bool(r_given.is_consistent)
|
|
114
|
-
else:
|
|
115
|
-
sc.given_preds_consistent = True
|
|
116
|
-
|
|
117
|
-
if list(sc.when):
|
|
118
|
-
r_when = await ix.check_scenario_consistent(
|
|
119
|
-
domain_model=dm,
|
|
120
|
-
scenario=sc,
|
|
121
|
-
check_state_preds=False,
|
|
122
|
-
check_action_preds=True,
|
|
123
|
-
)
|
|
124
|
-
sc.when_preds_consistent = bool(r_when.is_consistent)
|
|
125
|
-
else:
|
|
126
|
-
sc.when_preds_consistent = True
|
|
127
|
-
|
|
128
|
-
if list(sc.given) or list(sc.when):
|
|
129
|
-
r_all = await ix.check_scenario_consistent(
|
|
130
|
-
domain_model=dm,
|
|
131
|
-
scenario=sc,
|
|
132
|
-
check_state_preds=True,
|
|
133
|
-
check_action_preds=True,
|
|
134
|
-
)
|
|
135
|
-
sc.all_preds_consistent = bool(r_all.is_consistent)
|
|
136
|
-
else:
|
|
137
|
-
# No predicates at all => trivially consistent
|
|
138
|
-
sc.all_preds_consistent = True
|
|
139
|
-
|
|
140
|
-
except Exception:
|
|
141
|
-
# backend/compile errors => treat as inconsistent
|
|
142
|
-
sc.given_preds_consistent = False
|
|
143
|
-
sc.when_preds_consistent = False
|
|
144
|
-
sc.all_preds_consistent = False
|
|
145
|
-
|
|
146
|
-
sc.component_status = Valid() if sc.all_preds_consistent else Inconsistent()
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
async def _pairwise_overlaps(
|
|
150
|
-
ix: SpecLogicianImandraX,
|
|
151
|
-
*,
|
|
152
|
-
spec: Spec,
|
|
153
|
-
scenarios: list[Scenario]
|
|
154
|
-
) -> list[ScenarioOverlap]:
|
|
155
|
-
dm = spec.domain_model
|
|
156
|
-
checkable = [sc for sc in scenarios if _is_checkable(sc)]
|
|
157
|
-
out: list[ScenarioOverlap] = []
|
|
158
|
-
|
|
159
|
-
for i in range(len(checkable)):
|
|
160
|
-
for j in range(i + 1, len(checkable)):
|
|
161
|
-
a = checkable[i]
|
|
162
|
-
b = checkable[j]
|
|
163
|
-
|
|
164
|
-
inter = await ix.check_scenarios_intersect(domain_model=dm, sc_a=a, sc_b=b)
|
|
165
|
-
if inter.do_they_intersect:
|
|
166
|
-
witness = getattr(inter, "model_src", None)
|
|
167
|
-
details = "Scenarios overlap (intersection is satisfiable)."
|
|
168
|
-
if witness:
|
|
169
|
-
details += f"\n\nWitness model:\n{witness}"
|
|
170
|
-
|
|
171
|
-
out.append(
|
|
172
|
-
ScenarioOverlap(
|
|
173
|
-
scenario_name1=a.name,
|
|
174
|
-
scenario_name2=b.name,
|
|
175
|
-
details=details,
|
|
176
|
-
)
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
return out
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
async def _pairwise_consumed(
|
|
183
|
-
ix: SpecLogicianImandraX,
|
|
184
|
-
*,
|
|
185
|
-
spec: Spec,
|
|
186
|
-
scenarios: list[Scenario]
|
|
187
|
-
) -> list[ScenarioConsumed]:
|
|
188
|
-
"""
|
|
189
|
-
Compute directional "consumed" conflicts.
|
|
190
|
-
|
|
191
|
-
Requires ix.check_scenario_implies(domain_model=..., sc_a=..., sc_b=...)
|
|
192
|
-
which should return:
|
|
193
|
-
- ScenarioConsumed(...) when B => A
|
|
194
|
-
- None otherwise
|
|
195
|
-
|
|
196
|
-
Convention used here:
|
|
197
|
-
ScenarioConsumed(scenario_name1=A, scenario_name2=B) means B => A (B is subset of A).
|
|
198
|
-
"""
|
|
199
|
-
dm = spec.domain_model
|
|
200
|
-
if not hasattr(ix, "check_scenario_implies"):
|
|
201
|
-
return []
|
|
202
|
-
|
|
203
|
-
checkable = [sc for sc in scenarios if _is_checkable(sc)]
|
|
204
|
-
out: list[ScenarioConsumed] = []
|
|
205
|
-
|
|
206
|
-
for i in range(len(checkable)):
|
|
207
|
-
for j in range(len(checkable)):
|
|
208
|
-
if i == j:
|
|
209
|
-
continue
|
|
210
|
-
|
|
211
|
-
a = checkable[i] # A (more general)
|
|
212
|
-
b = checkable[j] # B (more specific)
|
|
213
|
-
|
|
214
|
-
# Conservative: only compare "consumption" when transitions are identical.
|
|
215
|
-
# (Predicate implication alone doesn't guarantee identical post-state behavior.)
|
|
216
|
-
if list(a.then) != list(b.then):
|
|
217
|
-
continue
|
|
218
|
-
|
|
219
|
-
# Expect: ScenarioConsumed | None
|
|
220
|
-
c = await ix.check_scenario_implies(domain_model=dm, sc_a=a, sc_b=b)
|
|
221
|
-
if c is not None:
|
|
222
|
-
out.append(c)
|
|
223
|
-
|
|
224
|
-
return out
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def _dedup_conflicts(conflicts: Iterable[ScenarioConflict]) -> list[ScenarioConflict]:
|
|
228
|
-
seen: set[tuple[str, str, str]] = set()
|
|
229
|
-
out: list[ScenarioConflict] = []
|
|
230
|
-
|
|
231
|
-
for c in conflicts:
|
|
232
|
-
if isinstance(c, ScenarioOverlap):
|
|
233
|
-
a, b = sorted([c.scenario_name1, c.scenario_name2])
|
|
234
|
-
key = ("overlap", a, b)
|
|
235
|
-
else:
|
|
236
|
-
# consumed is directional (A,B)
|
|
237
|
-
key = ("consumed", c.scenario_name1, c.scenario_name2)
|
|
238
|
-
|
|
239
|
-
if key in seen:
|
|
240
|
-
continue
|
|
241
|
-
seen.add(key)
|
|
242
|
-
out.append(c)
|
|
243
|
-
|
|
244
|
-
return out
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def _refresh_scenario_stats(spec: Spec) -> None:
|
|
248
|
-
"""
|
|
249
|
-
Recompute Spec-level scenario stats from the current scenario objects.
|
|
250
|
-
|
|
251
|
-
NOTE: This is GLOBAL across spec.scenarios (not just the selected subset).
|
|
252
|
-
"""
|
|
253
|
-
from speclogician.modeling.scenario import Missing, Inconsistent
|
|
254
|
-
|
|
255
|
-
spec.num_sc_total = len(spec.scenarios)
|
|
256
|
-
|
|
257
|
-
spec.num_sc_missing = sum(
|
|
258
|
-
1 for sc in spec.scenarios
|
|
259
|
-
if isinstance(getattr(sc, "component_status", None), Missing)
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
spec.num_sc_inconsistent = sum(
|
|
263
|
-
1 for sc in spec.scenarios
|
|
264
|
-
if isinstance(getattr(sc, "component_status", None), Inconsistent)
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
# "matched" is typically filled by your trace-matching pass; keep it robust.
|
|
268
|
-
# Prefer a field if you have it (e.g. sc.is_matched / sc.matched), otherwise 0.
|
|
269
|
-
def _is_matched(sc) -> bool:
|
|
270
|
-
v = getattr(sc, "is_matched", None)
|
|
271
|
-
if v is not None:
|
|
272
|
-
return bool(v)
|
|
273
|
-
v = getattr(sc, "matched", None)
|
|
274
|
-
if v is not None:
|
|
275
|
-
return bool(v)
|
|
276
|
-
return False
|
|
277
|
-
|
|
278
|
-
spec.num_sc_matched = sum(1 for sc in spec.scenarios if _is_matched(sc))
|
|
279
|
-
|
|
280
|
-
# ---- Conflicts (spec-level) ----
|
|
281
|
-
conflicts = list(getattr(spec, "scenario_conflicts", []) or [])
|
|
282
|
-
spec.num_sc_conflicted = len(conflicts)
|
|
283
|
-
|
|
284
|
-
# Count by kind (be tolerant if classes change)
|
|
285
|
-
from speclogician.modeling.conflict import ScenarioOverlap, ScenarioConsumed
|
|
286
|
-
|
|
287
|
-
spec.num_sc_overlap = sum(1 for c in conflicts if isinstance(c, ScenarioOverlap))
|
|
288
|
-
spec.num_sc_consumed = sum(1 for c in conflicts if isinstance(c, ScenarioConsumed))
|
|
289
|
-
|
|
290
|
-
def check_scenarios(
|
|
291
|
-
spec: Spec,
|
|
292
|
-
names: list[str] = [],
|
|
293
|
-
predicates: list[str] = [],
|
|
294
|
-
transitions: list[str] = [],
|
|
295
|
-
json_only: bool = False,
|
|
296
|
-
*,
|
|
297
|
-
analyze_conflicts: bool = True,
|
|
298
|
-
analyze_consumed: bool = True,
|
|
299
|
-
) -> None:
|
|
300
|
-
"""
|
|
301
|
-
Analyze scenarios:
|
|
302
|
-
- Filter scenarios by name/predicate/transition (conjunctive filters).
|
|
303
|
-
- For each selected scenario:
|
|
304
|
-
* set preds_missing/trans_missing
|
|
305
|
-
* set component_status (Missing/Valid/Inconsistent)
|
|
306
|
-
* set given_preds_consistent/when_preds_consistent/all_preds_consistent
|
|
307
|
-
- Optionally compute conflicts at Spec level:
|
|
308
|
-
* overlaps via intersection
|
|
309
|
-
* consumed via implication (if ix.check_scenario_implies exists)
|
|
310
|
-
|
|
311
|
-
Note: conflicts are stored on spec.scenario_conflicts.
|
|
312
|
-
"""
|
|
313
|
-
selected = _select_scenarios(
|
|
314
|
-
spec, names=names, predicates=predicates, transitions=transitions
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
if not json_only:
|
|
318
|
-
console.print(f"[bold]Logician[/bold] Checking scenarios: {len(selected)} selected")
|
|
319
|
-
|
|
320
|
-
async def _run() -> None:
|
|
321
|
-
async with SpecLogicianImandraX() as ix:
|
|
322
|
-
# per-scenario checks
|
|
323
|
-
for sc in selected:
|
|
324
|
-
await _analyze_one_scenario(ix, spec=spec, sc=sc)
|
|
325
|
-
|
|
326
|
-
if not analyze_conflicts:
|
|
327
|
-
return
|
|
328
|
-
|
|
329
|
-
overlaps = await _pairwise_overlaps(ix, spec=spec, scenarios=selected)
|
|
330
|
-
consumed = (
|
|
331
|
-
await _pairwise_consumed(ix, spec=spec, scenarios=selected)
|
|
332
|
-
if analyze_consumed
|
|
333
|
-
else []
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
# Replace only conflicts that touch selected scenarios (keep other conflicts intact)
|
|
337
|
-
selected_names = {sc.name for sc in selected}
|
|
338
|
-
kept: list[ScenarioConflict] = [
|
|
339
|
-
c
|
|
340
|
-
for c in spec.scenario_conflicts
|
|
341
|
-
if (c.scenario_name1 not in selected_names)
|
|
342
|
-
and (c.scenario_name2 not in selected_names)
|
|
343
|
-
]
|
|
344
|
-
|
|
345
|
-
spec.scenario_conflicts = _dedup_conflicts(
|
|
346
|
-
kept + list(overlaps) + list(consumed)
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
try:
|
|
350
|
-
asyncio.run(_run())
|
|
351
|
-
except RuntimeError:
|
|
352
|
-
# If already in an event loop (rare for CLI), fallback.
|
|
353
|
-
loop = asyncio.get_event_loop()
|
|
354
|
-
loop.run_until_complete(_run())
|
|
355
|
-
|
|
356
|
-
_refresh_scenario_stats(spec)
|
|
357
|
-
|
|
358
|
-
if not json_only:
|
|
359
|
-
n_missing = sum(isinstance(sc.component_status, Missing) for sc in selected)
|
|
360
|
-
n_incon = sum(isinstance(sc.component_status, Inconsistent) for sc in selected)
|
|
361
|
-
sel = {s.name for s in selected}
|
|
362
|
-
n_conf = sum(
|
|
363
|
-
1
|
|
364
|
-
for c in spec.scenario_conflicts
|
|
365
|
-
if (c.scenario_name1 in sel) or (c.scenario_name2 in sel)
|
|
366
|
-
)
|
|
367
|
-
console.print(
|
|
368
|
-
f"[dim]Scenarios checked={len(selected)} missing={n_missing} inconsistent={n_incon} conflicts={n_conf}[/dim]"
|
|
369
|
-
)
|
speclogician/logic/lib/traces.py
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Imandra Inc.
|
|
3
|
-
#
|
|
4
|
-
# speclogician/logic/lib/traces.py
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
import asyncio
|
|
8
|
-
|
|
9
|
-
from rich.progress import Progress, TaskID
|
|
10
|
-
|
|
11
|
-
from speclogician.data.container import ArtifactContainer
|
|
12
|
-
from speclogician.data.mapping import ArtifactMap, ElementType
|
|
13
|
-
from speclogician.data.artifact import TraceArtifact, TraceScenarioMatchResult, TraceIMLValidity
|
|
14
|
-
from speclogician.logic.api.client import SpecLogicianImandraX
|
|
15
|
-
from speclogician.modeling.spec import Spec
|
|
16
|
-
from speclogician.utils import IMLValidity, console
|
|
17
|
-
|
|
18
|
-
def check_traces(
|
|
19
|
-
spec: Spec,
|
|
20
|
-
art_cont: ArtifactContainer,
|
|
21
|
-
art_map: ArtifactMap,
|
|
22
|
-
traces: list[str],
|
|
23
|
-
json_only: bool = False,
|
|
24
|
-
) -> None:
|
|
25
|
-
dm = spec.domain_model
|
|
26
|
-
|
|
27
|
-
all_traces: list[TraceArtifact] = []
|
|
28
|
-
all_traces.extend(list(art_cont.test_traces))
|
|
29
|
-
all_traces.extend(list(art_cont.log_traces))
|
|
30
|
-
|
|
31
|
-
if traces:
|
|
32
|
-
want = set(traces)
|
|
33
|
-
to_check = [t for t in all_traces if t.art_id in want]
|
|
34
|
-
if not json_only:
|
|
35
|
-
missing = [tid for tid in traces if tid not in {t.art_id for t in all_traces}]
|
|
36
|
-
if missing:
|
|
37
|
-
console.print(f"[yellow]Logician[/yellow] Unknown trace id(s) requested: {missing}")
|
|
38
|
-
else:
|
|
39
|
-
to_check = all_traces
|
|
40
|
-
|
|
41
|
-
if not to_check:
|
|
42
|
-
if not json_only:
|
|
43
|
-
console.print("[bold cyan]Logician[/bold cyan] No traces to check.")
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
def _scenario_match_ok(res : TraceScenarioMatchResult) -> bool:
|
|
47
|
-
# TraceScenarioMatchResult has bool flags; mismatch is not an error
|
|
48
|
-
return bool(res.iml_valid) and res.given_matches and res.when_matches and res.transition_matches
|
|
49
|
-
|
|
50
|
-
async def _run_checks(progress: Progress | None = None, task_trace:TaskID|None=None) -> None:
|
|
51
|
-
async with SpecLogicianImandraX() as ix:
|
|
52
|
-
for tr in to_check:
|
|
53
|
-
|
|
54
|
-
if tr.art_id is None: # This should never be the case as we check this on the pydantic level
|
|
55
|
-
continue
|
|
56
|
-
|
|
57
|
-
# Clear old links for this artifact to avoid stale mapping
|
|
58
|
-
art_map.rem_element(ElementType.ArtifactType, tr.art_id)
|
|
59
|
-
|
|
60
|
-
# 1) IML validity
|
|
61
|
-
try:
|
|
62
|
-
v = await ix.check_trace_iml_validity(tr, dm)
|
|
63
|
-
# v: TraceIMLValidity(iml_valid: IMLValidity, err: str|None)
|
|
64
|
-
tr.iml_validity = v
|
|
65
|
-
except Exception as e:
|
|
66
|
-
tr.iml_validity = TraceIMLValidity(iml_valid=IMLValidity.UNKNOWN, err=str(e))
|
|
67
|
-
if progress is not None and task_trace is not None:
|
|
68
|
-
progress.advance(task_trace)
|
|
69
|
-
continue
|
|
70
|
-
|
|
71
|
-
# Only proceed to scenario matching if trace is VALID IML
|
|
72
|
-
if tr.iml_validity.iml_valid != IMLValidity.VALID:
|
|
73
|
-
if progress is not None and task_trace is not None:
|
|
74
|
-
progress.advance(task_trace)
|
|
75
|
-
continue
|
|
76
|
-
|
|
77
|
-
# 2) Match against scenarios; record matches
|
|
78
|
-
for sc in spec.scenarios:
|
|
79
|
-
try:
|
|
80
|
-
m : TraceScenarioMatchResult = await ix.check_trace_match(tr, sc, dm)
|
|
81
|
-
except Exception:
|
|
82
|
-
# tool failure => skip (don't manufacture mismatch errors)
|
|
83
|
-
continue
|
|
84
|
-
|
|
85
|
-
if _scenario_match_ok(m):
|
|
86
|
-
art_map.add_connection(art_id=tr.art_id, comp_name=sc.comp_id)
|
|
87
|
-
|
|
88
|
-
if progress is not None and task_trace is not None:
|
|
89
|
-
progress.advance(task_trace)
|
|
90
|
-
|
|
91
|
-
if not json_only:
|
|
92
|
-
console.print(
|
|
93
|
-
f"[bold cyan]Logician[/bold cyan] Checking traces ({len(to_check)}) "
|
|
94
|
-
f"against scenarios ({len(spec.scenarios)})"
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
with Progress(transient=True) as progress:
|
|
98
|
-
task_trace = progress.add_task("Checking traces", total=len(to_check))
|
|
99
|
-
try:
|
|
100
|
-
asyncio.run(_run_checks(progress=progress, task_trace=task_trace))
|
|
101
|
-
except Exception as e:
|
|
102
|
-
console.print(f"[red]Logician[/red] Trace checking failed: {e}")
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
n_valid = sum(1 for t in to_check if t.iml_validity.iml_valid == IMLValidity.VALID)
|
|
106
|
-
n_invalid = sum(1 for t in to_check if t.iml_validity.iml_valid == IMLValidity.INVALID)
|
|
107
|
-
n_unknown = sum(1 for t in to_check if t.iml_validity.iml_valid == IMLValidity.UNKNOWN)
|
|
108
|
-
console.print(f"[green]✓[/green] Traces checked. valid={n_valid}, invalid={n_invalid}, unknown={n_unknown}")
|
|
109
|
-
else:
|
|
110
|
-
try:
|
|
111
|
-
asyncio.run(_run_checks())
|
|
112
|
-
except Exception:
|
|
113
|
-
return
|
|
114
|
-
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Imandra Inc.
|
|
3
|
-
#
|
|
4
|
-
# speclogician/logic/lib/transitions.py
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
import asyncio
|
|
8
|
-
|
|
9
|
-
from rich.progress import Progress
|
|
10
|
-
|
|
11
|
-
from speclogician.modeling.spec import Spec
|
|
12
|
-
from speclogician.logic.api.client import SpecLogicianImandraX
|
|
13
|
-
from speclogician.utils import IMLValidity, console
|
|
14
|
-
|
|
15
|
-
def check_transitions(
|
|
16
|
-
spec: Spec,
|
|
17
|
-
names: list[str] = [],
|
|
18
|
-
json_only: bool = False,
|
|
19
|
-
) -> None:
|
|
20
|
-
"""
|
|
21
|
-
Check transition IML validity via ImandraX and write results into transition objects:
|
|
22
|
-
- transition.is_iml_valid
|
|
23
|
-
- transition.iml_error
|
|
24
|
-
If `names` is non-empty, checks only those transition names; otherwise checks all.
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
dm = spec.domain_model
|
|
28
|
-
all_trans = list(dm.transitions)
|
|
29
|
-
|
|
30
|
-
if names:
|
|
31
|
-
name_set = set(names)
|
|
32
|
-
trans_to_check = [t for t in all_trans if t.name in name_set]
|
|
33
|
-
|
|
34
|
-
if not json_only:
|
|
35
|
-
known = {t.name for t in all_trans}
|
|
36
|
-
missing = [n for n in names if n not in known]
|
|
37
|
-
if missing:
|
|
38
|
-
console.print(f"[yellow]Logician[/yellow] Unknown transition name(s) requested: {missing}")
|
|
39
|
-
else:
|
|
40
|
-
trans_to_check = all_trans
|
|
41
|
-
|
|
42
|
-
def _errs_to_str(res: object) -> str:
|
|
43
|
-
es = getattr(res, "errors", None) or []
|
|
44
|
-
if es:
|
|
45
|
-
return "\n".join(str(e) for e in es)
|
|
46
|
-
return "ImandraX eval_model: has_errors=True"
|
|
47
|
-
|
|
48
|
-
if not json_only:
|
|
49
|
-
console.print("[bold cyan]Logician[/bold cyan] Checking transition IML validity")
|
|
50
|
-
|
|
51
|
-
async def _check_all() -> None:
|
|
52
|
-
async with SpecLogicianImandraX() as ix:
|
|
53
|
-
for t in trans_to_check:
|
|
54
|
-
src = dm.make_iml_model([], [], [t.name])
|
|
55
|
-
try:
|
|
56
|
-
r = await ix.load_model(src)
|
|
57
|
-
has_err = bool(getattr(r, "has_errors", False))
|
|
58
|
-
t.is_iml_valid = IMLValidity.INVALID if has_err else IMLValidity.VALID
|
|
59
|
-
t.iml_error = None if not has_err else _errs_to_str(r)
|
|
60
|
-
except Exception as e:
|
|
61
|
-
t.is_iml_valid = IMLValidity.UNKNOWN
|
|
62
|
-
t.iml_error = str(e)
|
|
63
|
-
|
|
64
|
-
if not json_only:
|
|
65
|
-
with Progress(transient=True) as progress:
|
|
66
|
-
task = progress.add_task("Checking transitions", total=len(trans_to_check))
|
|
67
|
-
|
|
68
|
-
async def _check_with_progress() -> None:
|
|
69
|
-
async with SpecLogicianImandraX() as ix:
|
|
70
|
-
for t in trans_to_check:
|
|
71
|
-
src = dm.make_iml_model([], [], [t.name])
|
|
72
|
-
try:
|
|
73
|
-
r = await ix.load_model(src)
|
|
74
|
-
has_err = bool(getattr(r, "has_errors", False))
|
|
75
|
-
t.is_iml_valid = IMLValidity.INVALID if has_err else IMLValidity.VALID
|
|
76
|
-
t.iml_error = None if not has_err else _errs_to_str(r)
|
|
77
|
-
except Exception as e:
|
|
78
|
-
t.is_iml_valid = IMLValidity.UNKNOWN
|
|
79
|
-
t.iml_error = str(e)
|
|
80
|
-
progress.advance(task)
|
|
81
|
-
|
|
82
|
-
try:
|
|
83
|
-
asyncio.run(_check_with_progress())
|
|
84
|
-
except Exception as e:
|
|
85
|
-
for t in trans_to_check:
|
|
86
|
-
t.is_iml_valid = IMLValidity.UNKNOWN
|
|
87
|
-
t.iml_error = str(e)
|
|
88
|
-
else:
|
|
89
|
-
try:
|
|
90
|
-
asyncio.run(_check_all())
|
|
91
|
-
except Exception as e:
|
|
92
|
-
for t in trans_to_check:
|
|
93
|
-
t.is_iml_valid = IMLValidity.UNKNOWN
|
|
94
|
-
t.iml_error = str(e)
|
|
95
|
-
|
|
96
|
-
# Refresh domain-model transition stats (global, not just filtered set)
|
|
97
|
-
dm.num_trans_total = len(dm.transitions)
|
|
98
|
-
dm.num_trans_valid_logic = sum(1 for t in dm.transitions if t.is_iml_valid == IMLValidity.VALID)
|
|
99
|
-
|
|
100
|
-
if not json_only:
|
|
101
|
-
console.print(
|
|
102
|
-
f"[green]✓[/green] Transitions checked. "
|
|
103
|
-
f"valid={dm.num_trans_valid_logic}/{dm.num_trans_total}"
|
|
104
|
-
)
|