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.
Files changed (153) hide show
  1. speclogician/agent/funcs.py +29 -0
  2. speclogician/cmd/agent_cmd.py +89 -0
  3. speclogician/cmd/data_cmd.py +24 -0
  4. speclogician/cmd/model_cmd.py +42 -0
  5. speclogician/cmd/overlay_cmd.py +30 -0
  6. speclogician/cmd/scenario_cmd.py +61 -0
  7. speclogician/cmd/state_cmd.py +52 -0
  8. speclogician/data/artifact.py +8 -50
  9. speclogician/data/container.py +18 -384
  10. speclogician/data/mapping.py +18 -17
  11. speclogician/data/refs.py +12 -11
  12. speclogician/data/reports.py +11 -0
  13. speclogician/data/traces.py +15 -6
  14. speclogician/llms/llmtools.py +102 -0
  15. speclogician/llms/overlay.py +264 -0
  16. speclogician/main.py +36 -102
  17. speclogician/modeling/__init__.py +0 -31
  18. speclogician/modeling/component.py +4 -60
  19. speclogician/modeling/conflict.py +5 -19
  20. speclogician/modeling/domain.py +93 -280
  21. speclogician/modeling/model.py +206 -0
  22. speclogician/modeling/predicates.py +20 -22
  23. speclogician/modeling/report.py +33 -0
  24. speclogician/modeling/scenario.py +119 -87
  25. speclogician/sl_cmd.py +76 -0
  26. speclogician/state/change.py +98 -378
  27. speclogician/state/state.py +183 -399
  28. speclogician/tui/box.tcss +10 -0
  29. speclogician/tui/tui.py +131 -0
  30. speclogician/utils/__init__.py +1 -70
  31. speclogician/utils/imx.py +195 -0
  32. speclogician/utils/load.py +25 -147
  33. speclogician/utils/prompt.md +1 -325
  34. speclogician-0.0.0.dev1.dist-info/METADATA +21 -0
  35. speclogician-0.0.0.dev1.dist-info/RECORD +43 -0
  36. speclogician/commands/__init__.py +0 -15
  37. speclogician/commands/cmd_ch.py +0 -616
  38. speclogician/commands/cmd_find.py +0 -256
  39. speclogician/commands/cmd_view.py +0 -202
  40. speclogician/commands/runner.py +0 -149
  41. speclogician/commands/utils.py +0 -101
  42. speclogician/demos/.DS_Store +0 -0
  43. speclogician/demos/cmd_demo.py +0 -278
  44. speclogician/demos/loader.py +0 -135
  45. speclogician/demos/model.py +0 -27
  46. speclogician/demos/runner.py +0 -51
  47. speclogician/logic/__init__.py +0 -11
  48. speclogician/logic/api/__init__.py +0 -29
  49. speclogician/logic/api/client.py +0 -606
  50. speclogician/logic/api/decomp.py +0 -67
  51. speclogician/logic/api/scenario.py +0 -102
  52. speclogician/logic/api/traces.py +0 -59
  53. speclogician/logic/lib/__init__.py +0 -19
  54. speclogician/logic/lib/complement.py +0 -107
  55. speclogician/logic/lib/domain_model.py +0 -59
  56. speclogician/logic/lib/predicates.py +0 -151
  57. speclogician/logic/lib/scenarios.py +0 -369
  58. speclogician/logic/lib/traces.py +0 -114
  59. speclogician/logic/lib/transitions.py +0 -104
  60. speclogician/logic/main.py +0 -246
  61. speclogician/logic/strings.py +0 -194
  62. speclogician/logic/utils.py +0 -135
  63. speclogician/modeling/complement.py +0 -104
  64. speclogician/modeling/spec.py +0 -306
  65. speclogician/modeling/spec_stats.py +0 -39
  66. speclogician/presentation/api.py +0 -244
  67. speclogician/presentation/builders/_links.py +0 -44
  68. speclogician/presentation/builders/container.py +0 -53
  69. speclogician/presentation/builders/data_artifact.py +0 -42
  70. speclogician/presentation/builders/domain.py +0 -54
  71. speclogician/presentation/builders/instances_list.py +0 -38
  72. speclogician/presentation/builders/predicate.py +0 -51
  73. speclogician/presentation/builders/recommendations.py +0 -41
  74. speclogician/presentation/builders/scenario.py +0 -41
  75. speclogician/presentation/builders/scenario_complement.py +0 -82
  76. speclogician/presentation/builders/smart_find.py +0 -39
  77. speclogician/presentation/builders/spec.py +0 -39
  78. speclogician/presentation/builders/state_diff.py +0 -150
  79. speclogician/presentation/builders/state_instance.py +0 -42
  80. speclogician/presentation/builders/state_instance_summary.py +0 -84
  81. speclogician/presentation/builders/trace.py +0 -58
  82. speclogician/presentation/ctx.py +0 -38
  83. speclogician/presentation/models/container.py +0 -44
  84. speclogician/presentation/models/data_artifact.py +0 -33
  85. speclogician/presentation/models/domain.py +0 -50
  86. speclogician/presentation/models/instances_list.py +0 -23
  87. speclogician/presentation/models/predicate.py +0 -60
  88. speclogician/presentation/models/recommendations.py +0 -34
  89. speclogician/presentation/models/scenario.py +0 -31
  90. speclogician/presentation/models/scenario_complement.py +0 -40
  91. speclogician/presentation/models/smart_find.py +0 -34
  92. speclogician/presentation/models/spec.py +0 -32
  93. speclogician/presentation/models/state_diff.py +0 -34
  94. speclogician/presentation/models/state_instance.py +0 -31
  95. speclogician/presentation/models/state_instance_summary.py +0 -102
  96. speclogician/presentation/models/trace.py +0 -42
  97. speclogician/presentation/preview/__init__.py +0 -13
  98. speclogician/presentation/preview/cli.py +0 -50
  99. speclogician/presentation/preview/fixtures/__init__.py +0 -205
  100. speclogician/presentation/preview/fixtures/artifact_container.py +0 -150
  101. speclogician/presentation/preview/fixtures/data_artifact.py +0 -144
  102. speclogician/presentation/preview/fixtures/domain_model.py +0 -162
  103. speclogician/presentation/preview/fixtures/instances_list.py +0 -162
  104. speclogician/presentation/preview/fixtures/predicate.py +0 -184
  105. speclogician/presentation/preview/fixtures/scenario.py +0 -84
  106. speclogician/presentation/preview/fixtures/scenario_complement.py +0 -81
  107. speclogician/presentation/preview/fixtures/smart_find.py +0 -140
  108. speclogician/presentation/preview/fixtures/spec.py +0 -95
  109. speclogician/presentation/preview/fixtures/state_diff.py +0 -158
  110. speclogician/presentation/preview/fixtures/state_instance.py +0 -128
  111. speclogician/presentation/preview/fixtures/state_instance_summary.py +0 -80
  112. speclogician/presentation/preview/fixtures/trace.py +0 -206
  113. speclogician/presentation/preview/registry.py +0 -42
  114. speclogician/presentation/renderers/__init__.py +0 -24
  115. speclogician/presentation/renderers/container.py +0 -136
  116. speclogician/presentation/renderers/data_artifact.py +0 -144
  117. speclogician/presentation/renderers/domain.py +0 -123
  118. speclogician/presentation/renderers/instances_list.py +0 -120
  119. speclogician/presentation/renderers/predicate.py +0 -180
  120. speclogician/presentation/renderers/recommendations.py +0 -90
  121. speclogician/presentation/renderers/scenario.py +0 -94
  122. speclogician/presentation/renderers/scenario_complement.py +0 -59
  123. speclogician/presentation/renderers/smart_find.py +0 -307
  124. speclogician/presentation/renderers/spec.py +0 -105
  125. speclogician/presentation/renderers/state_diff.py +0 -102
  126. speclogician/presentation/renderers/state_instance.py +0 -82
  127. speclogician/presentation/renderers/state_instance_summary.py +0 -143
  128. speclogician/presentation/renderers/trace.py +0 -122
  129. speclogician/shell/app.py +0 -170
  130. speclogician/shell/shell_ch.py +0 -263
  131. speclogician/shell/shell_view.py +0 -153
  132. speclogician/state/change_result.py +0 -32
  133. speclogician/state/diff.py +0 -191
  134. speclogician/state/inst.py +0 -574
  135. speclogician/state/recommendation.py +0 -13
  136. speclogician/state/recommender.py +0 -577
  137. speclogician/state/state_stats.py +0 -133
  138. speclogician/tui/__init__.py +0 -0
  139. speclogician/tui/app.py +0 -257
  140. speclogician/tui/app.tcss +0 -160
  141. speclogician/tui/demo.py +0 -45
  142. speclogician/tui/images/speclogician-full.png +0 -0
  143. speclogician/tui/images/speclogician-minimal.png +0 -0
  144. speclogician/tui/main_screen.py +0 -454
  145. speclogician/tui/splash_screen.py +0 -51
  146. speclogician/tui/stats_screen.py +0 -125
  147. speclogician/utils/testing.py +0 -151
  148. speclogician-0.0.0b1.dist-info/METADATA +0 -116
  149. speclogician-0.0.0b1.dist-info/RECORD +0 -139
  150. /speclogician/{presentation → agent}/__init__.py +0 -0
  151. /speclogician/{presentation/builders → cmd}/__init__.py +0 -0
  152. /speclogician/{presentation/models → llms}/__init__.py +0 -0
  153. {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
- )
@@ -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
- )