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,246 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/logic/main.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- from ..modeling.spec import Spec
10
- from ..data.container import ArtifactContainer
11
- from ..data.mapping import ArtifactMap
12
-
13
- from ..state.change import (
14
- StateChange,
15
- DomainModelBaseEdit,
16
- PredicateAdd,
17
- PredicateEdit,
18
- PredicateRemove,
19
- TransitionAdd,
20
- TransitionEdit,
21
- TransitionRemove,
22
- ScenarioAdd,
23
- ScenarioEdit,
24
- ScenarioRemove,
25
- AddTestTrace,
26
- EditTestTrace,
27
- AddLogTrace,
28
- EditLogTrace
29
- )
30
-
31
- from .lib.domain_model import check_domain_model
32
- from .lib.predicates import check_predicates
33
- from .lib.transitions import check_transitions
34
- from .lib.scenarios import check_scenarios
35
- from .lib.traces import check_traces
36
- from .lib.complement import maybe_update_spec_complement
37
-
38
- from ..utils import console
39
-
40
-
41
- def _hdr(title: str, subtitle: str | None = None, json_only: bool = False) -> None:
42
- if json_only:
43
- return
44
- from rich.panel import Panel
45
- from rich.text import Text
46
-
47
- console.print(
48
- Panel(
49
- Text(subtitle or ""),
50
- title=f"[bold]Logician[/bold] · {title}",
51
- border_style="cyan",
52
- )
53
- )
54
-
55
-
56
- def _note(msg: str, json_only: bool = False) -> None:
57
- if not json_only:
58
- console.print(msg)
59
-
60
- def analyze_change(
61
- spec: Spec,
62
- art_cont: ArtifactContainer,
63
- art_map: ArtifactMap,
64
- change: StateChange,
65
- json_only: bool = False,
66
- ) -> None:
67
- """
68
- Top-level entry for logical analysis post-change event.
69
-
70
- Only performs logic checks when relevant.
71
- All other change types are treated as no-ops here.
72
- """
73
-
74
- # ----------------------------
75
- # Domain base model changed
76
- # ----------------------------
77
- if isinstance(change, DomainModelBaseEdit):
78
- _hdr("DomainModelBaseEdit", "Rechecking domain model and all dependent logic", json_only=json_only)
79
- check_domain_model(spec, json_only=json_only)
80
- check_predicates(spec, json_only=json_only)
81
- check_transitions(spec, json_only=json_only)
82
- check_scenarios(spec, json_only=json_only)
83
- check_traces(spec, art_cont=art_cont, art_map=art_map, traces=[], json_only=json_only)
84
-
85
- try:
86
- maybe_update_spec_complement(spec)
87
- except Exception as e:
88
- _note(f"[yellow]⚠[/yellow] Complement update failed (non-fatal): {type(e).__name__}: {e}", json_only=json_only)
89
-
90
- _note("[green]✓[/green] Domain analysis complete", json_only=json_only)
91
- return
92
-
93
- # ----------------------------
94
- # Predicates changed
95
- # ----------------------------
96
- if isinstance(change, (PredicateAdd, PredicateEdit)):
97
- _hdr("Predicate change", f"Predicate: [bold]{change.pred_name}[/bold]", json_only=json_only)
98
- check_predicates(spec, names=[change.pred_name], json_only=json_only)
99
- check_scenarios(spec, predicates=[change.pred_name], json_only=json_only)
100
- check_traces(spec, art_cont=art_cont, art_map=art_map, traces=[], json_only=json_only)
101
-
102
- try:
103
- maybe_update_spec_complement(spec)
104
- except Exception as e:
105
- _note(f"[yellow]⚠[/yellow] Complement update failed (non-fatal): {type(e).__name__}: {e}", json_only=json_only)
106
-
107
- _note("[green]✓[/green] Predicate analysis complete", json_only=json_only)
108
- return
109
-
110
- if isinstance(change, PredicateRemove):
111
- _hdr("Predicate removed", f"[bold]{change.pred_name}[/bold]", json_only=json_only)
112
- check_scenarios(spec, predicates=[change.pred_name], json_only=json_only)
113
- check_traces(spec, art_cont=art_cont, art_map=art_map, traces=[], json_only=json_only)
114
-
115
- try:
116
- maybe_update_spec_complement(spec)
117
- except Exception as e:
118
- _note(f"[yellow]⚠[/yellow] Complement update failed (non-fatal): {type(e).__name__}: {e}", json_only=json_only)
119
-
120
- _note("[green]✓[/green] Scenario refresh complete", json_only=json_only)
121
- return
122
-
123
- # ----------------------------
124
- # Transitions changed
125
- # ----------------------------
126
- if isinstance(change, (TransitionAdd, TransitionEdit)):
127
- _hdr("Transition change", f"Transition: [bold]{change.trans_name}[/bold]", json_only=json_only)
128
- check_transitions(spec, names=[change.trans_name], json_only=json_only)
129
- check_scenarios(spec, transitions=[change.trans_name], json_only=json_only)
130
- check_traces(spec, art_cont=art_cont, art_map=art_map, traces=[], json_only=json_only)
131
-
132
- try:
133
- maybe_update_spec_complement(spec)
134
- except Exception as e:
135
- _note(f"[yellow]⚠[/yellow] Complement update failed (non-fatal): {type(e).__name__}: {e}", json_only=json_only)
136
-
137
- _note("[green]✓[/green] Transition analysis complete", json_only=json_only)
138
- return
139
-
140
- if isinstance(change, TransitionRemove):
141
- _hdr("Transition removed", f"[bold]{change.trans_name}[/bold]", json_only=json_only)
142
- check_scenarios(spec, transitions=[change.trans_name], json_only=json_only)
143
- check_traces(spec, art_cont=art_cont, art_map=art_map, traces=[], json_only=json_only)
144
-
145
- try:
146
- maybe_update_spec_complement(spec)
147
- except Exception as e:
148
- _note(f"[yellow]⚠[/yellow] Complement update failed (non-fatal): {type(e).__name__}: {e}", json_only=json_only)
149
-
150
- _note("[green]✓[/green] Scenario refresh complete", json_only=json_only)
151
- return
152
-
153
- # ----------------------------
154
- # Scenarios changed
155
- # ----------------------------
156
- if isinstance(change, (ScenarioAdd, ScenarioEdit)):
157
- _hdr("Scenario change", f"Scenario: [bold]{change.scenario_name}[/bold]", json_only=json_only)
158
- check_scenarios(spec, names=[change.scenario_name], json_only=json_only)
159
- check_traces(spec, art_cont=art_cont, art_map=art_map, traces=[], json_only=json_only)
160
-
161
- try:
162
- maybe_update_spec_complement(spec)
163
- except Exception as e:
164
- _note(f"[yellow]⚠[/yellow] Complement update failed (non-fatal): {type(e).__name__}: {e}", json_only=json_only)
165
-
166
- _note("[green]✓[/green] Scenario analysis complete", json_only=json_only)
167
- return
168
-
169
- if isinstance(change, ScenarioRemove):
170
- _hdr("Scenario removed", "Refreshing scenarios, predicates, and traces", json_only=json_only)
171
- check_scenarios(spec, json_only=json_only)
172
- check_predicates(spec, json_only=json_only)
173
- check_traces(spec=spec, art_cont=art_cont, art_map=art_map, traces=[], json_only=json_only)
174
-
175
- try:
176
- maybe_update_spec_complement(spec)
177
- except Exception as e:
178
- _note(f"[yellow]⚠[/yellow] Complement update failed (non-fatal): {type(e).__name__}: {e}", json_only=json_only)
179
-
180
- _note("[green]✓[/green] Full refresh complete", json_only=json_only)
181
- return
182
-
183
- # ----------------------------
184
- # Trace artifacts changed
185
- # ----------------------------
186
- if isinstance(change, AddTestTrace):
187
- if not art_cont.test_traces:
188
- return
189
-
190
- tt = art_cont.test_traces[-1]
191
- # best-effort sanity check; no-op if mismatch
192
- if change.art_id is not None and tt.art_id != change.art_id:
193
- pass
194
-
195
- assert tt.art_id is not None
196
-
197
- check_traces(
198
- spec=spec,
199
- art_cont=art_cont,
200
- art_map=art_map,
201
- traces=[tt.art_id],
202
- json_only=json_only,
203
- )
204
-
205
- _note("[green]✓[/green] Trace analysis complete", json_only=json_only)
206
- return
207
-
208
- if isinstance(change, AddLogTrace):
209
- if not art_cont.log_traces:
210
- return
211
-
212
- lt = art_cont.log_traces[-1]
213
- if change.art_id is not None and lt.art_id != change.art_id:
214
- pass
215
-
216
- assert lt.art_id is not None
217
-
218
- check_traces(
219
- spec=spec,
220
- art_cont=art_cont,
221
- art_map=art_map,
222
- traces=[lt.art_id],
223
- json_only=json_only,
224
- )
225
-
226
- _note("[green]✓[/green] Trace analysis complete", json_only=json_only)
227
- return
228
-
229
- if isinstance(change, (EditTestTrace, EditLogTrace)):
230
- check_traces(
231
- spec=spec,
232
- art_cont=art_cont,
233
- art_map=art_map,
234
- traces=[change.art_id],
235
- json_only=json_only,
236
- )
237
-
238
- _note("[green]✓[/green] Trace analysis complete", json_only=json_only)
239
- return
240
-
241
- # ----------------------------
242
- # Explicit no-op branch
243
- # ----------------------------
244
- # All remaining change types (data artifacts, mapping edits, etc.)
245
- # are intentionally ignored here and handled elsewhere.
246
- return
@@ -1,194 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/logic/strings.py
5
- #
6
-
7
-
8
- from __future__ import annotations
9
-
10
- import re
11
- from typing import Sequence
12
-
13
- from ..modeling.scenario import Scenario
14
-
15
- def parens(x: str) -> str:
16
- x = x.strip()
17
- return x if (x.startswith("(") and x.endswith(")")) else f"({x})"
18
-
19
-
20
- def and_all(conjuncts: Sequence[str]) -> str:
21
- if not conjuncts:
22
- return "true"
23
- return " && ".join(parens(c) for c in conjuncts)
24
-
25
-
26
- def mk_predicates_only_instance_src(
27
- name: str,
28
- *,
29
- given: Sequence[str], # state predicate names: p : state -> bool
30
- when: Sequence[str], # action predicate names: q : state -> action -> bool (state first!)
31
- state_type: str = "state",
32
- action_type: str = "action",
33
- state_var: str = "s",
34
- action_var: str = "a",
35
- ) -> str:
36
- """
37
- Consistency query: only predicates (given/when), no transitions.
38
-
39
- Produces:
40
- (p1 s) && (q1 s a) && ...
41
- """
42
- lines : list[str] = ["fun (s : state) (a : action) -> "]
43
-
44
- exprs: list[str] = []
45
- exprs += [f"{p} {state_var}" for p in given]
46
- exprs += [f"{q} {state_var} {action_var}" for q in when]
47
-
48
- lines.append(f" {and_all(exprs)}")
49
- result = "\n".join(lines)
50
-
51
- return result
52
-
53
-
54
- def mk_same_action_trace_instance_src(
55
- name: str,
56
- *,
57
- given: Sequence[str], # state predicate names
58
- when: Sequence[str], # action predicate names
59
- transitions: Sequence[str], # transition fn names: t : state -> action -> state
60
- state_type: str = "state",
61
- action_type: str = "action",
62
- state0_var: str = "s0",
63
- action_var: str = "a",
64
- when_each_step: bool = True, # apply action predicates at each pre-state
65
- ) -> str:
66
- """
67
- Trace query: transitions applied sequentially using the SAME action `a`.
68
-
69
- Produces:
70
-
71
- instance <name> :
72
- let s0 : state = s0 in
73
- let a : action = a in
74
- let s1 = t0 s0 a in
75
- let s2 = t1 s1 a in
76
- (given(s0)) && (when(s0,a)) && (when(s1,a)) ...
77
-
78
- Note: for N transitions, `when` is applied to s0..s{N-1} if when_each_step=True.
79
- """
80
- lines: list[str] = [f"instance {name} :"]
81
-
82
- # typed existentials
83
- lines.append(f" let {state0_var} : {state_type} = {state0_var} in")
84
- lines.append(f" let {action_var} : {action_type} = {action_var} in")
85
-
86
- # let-chain states
87
- # s0 already exists; create s1..sN
88
- for i, tname in enumerate(transitions):
89
- si = state0_var if i == 0 else f"s{i}"
90
- si1 = f"s{i+1}"
91
- lines.append(f" let {si1} = {tname} {si} {action_var} in")
92
-
93
- exprs: list[str] = []
94
- exprs += [f"{p} {state0_var}" for p in given]
95
-
96
- if when_each_step:
97
- for i in range(len(transitions)):
98
- si = state0_var if i == 0 else f"s{i}"
99
- exprs += [f"{q} {si} {action_var}" for q in when]
100
- else:
101
- exprs += [f"{q} {state0_var} {action_var}" for q in when]
102
-
103
- lines.append(f" {and_all(exprs)}")
104
- return "\n".join(lines)
105
-
106
-
107
- def mk_scenario_expr(sc: Scenario, *, s_var: str = "s", a_var: str = "a") -> str:
108
- parts: list[str] = []
109
- parts += [f"({p} {s_var})" for p in sc.given]
110
- parts += [f"({q} {s_var} {a_var})" for q in sc.when]
111
- return "true" if not parts else " && ".join(parts)
112
-
113
- def mk_spec_disjunction_expr(scenarios: Sequence[Scenario]) -> str:
114
- disj = " || ".join(f"({mk_scenario_expr(sc)})" for sc in scenarios)
115
- return "false" if not disj else disj
116
-
117
- def mk_goal_def(goal_name: str, goal_expr: str, *, needs_action: bool) -> str:
118
- if needs_action:
119
- return (
120
- f"let {goal_name} (s : state) (a : action) : bool =\n"
121
- f" {goal_expr}\n"
122
- )
123
- else:
124
- return (
125
- f"let {goal_name} (s : state) : bool =\n"
126
- f" {goal_expr}\n"
127
- )
128
-
129
- def collect_pred_names(scenarios: Sequence[Scenario]) -> tuple[list[str], list[str]]:
130
- state_names: list[str] = []
131
- action_names: list[str] = []
132
- for sc in scenarios:
133
- state_names += list(sc.given)
134
- action_names += list(sc.when)
135
- return state_names, action_names
136
-
137
- LET_RE = re.compile(r"^\s*let\s+([a-zA-Z_][a-zA-Z0-9_']*)\s*(?::\s*[^=]+)?=\s*(.+)$", re.DOTALL)
138
-
139
- def normalize_optional(s: str | None) -> str | None:
140
- if s is None:
141
- return None
142
- s2 = s.strip()
143
- return s2 if s2 else None
144
-
145
- def mk_typed_let(binding_src: str, *, expected_type: str, default_name: str) -> tuple[str, str]:
146
- """
147
- Returns: (let_src, bound_name)
148
- Accepts either:
149
- - `let x = ...` (or `let x : t = ...`) -> keeps x
150
- - `<expr>` -> wraps as `let <default_name> : expected_type = (<expr>)`
151
- """
152
- m = LET_RE.match(binding_src.strip())
153
- if m:
154
- name = m.group(1)
155
- # Keep user's let as-is; we just trust their annotation (or lack of).
156
- # (Type errors will be caught by eval_model.)
157
- return binding_src.strip(), name
158
-
159
- name = default_name
160
- let_src = f"let {name} : {expected_type} = (\n{binding_src.strip()}\n)"
161
- return let_src, name
162
-
163
- def mk_instance_src_for_expr(*, name: str, expr: str, needs_action: bool) -> str:
164
- # Assumes `state` and (optionally) `action` exist in the model context.
165
- # `expr` must be boolean and can mention `s` and optionally `a`.
166
- if needs_action:
167
- return f"""
168
- instance {name} =
169
- {{ s : state; a : action }}
170
- where {expr}
171
- """
172
- return f"""
173
- instance {name} =
174
- {{ s : state }}
175
- where {expr}
176
- """
177
-
178
- def errs_to_string(eval_res: object) -> str | None:
179
- """
180
- Your eval_model result looks like it may have .has_errors and .errors.
181
- Convert to a multi-line string.
182
- """
183
- if not bool(getattr(eval_res, "has_errors", False)):
184
- return None
185
-
186
- errs = getattr(eval_res, "errors", None)
187
- if not errs:
188
- return "Domain/trace model has errors"
189
-
190
- # errs items could be strings or structured; stringify safely
191
- lines: list[str] = []
192
- for e in errs:
193
- lines.append(str(e))
194
- return "\n".join(lines)
@@ -1,135 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/logic/utils.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- from typing import Any, Literal
10
-
11
- InstanceStatus = Literal["sat", "unsat", "unknown", "err"]
12
-
13
- _ONEOF_CANDIDATES = ("result", "res", "outcome", "status", "response")
14
-
15
- def instance_status(res: Any) -> InstanceStatus:
16
-
17
- # 1) Prefer oneof introspection if available
18
- for oneof_name in _ONEOF_CANDIDATES:
19
- try:
20
- which = res.WhichOneof(oneof_name)
21
- except Exception:
22
- continue
23
- if which in ("sat", "unsat", "unknown", "err"):
24
- return which # type: ignore[return-value]
25
-
26
- # 2) Fallback to HasField checks
27
- for k in ("sat", "unsat", "unknown", "err"):
28
- try:
29
- if res.HasField(k):
30
- return k # type: ignore[return-value]
31
- except Exception:
32
- pass
33
-
34
- # 3) Conservative fallback
35
- return "unknown"
36
-
37
-
38
- def instance_is_sat(res: Any) -> bool:
39
- return res.sat is not None
40
-
41
- def instance_model(res: Any) -> Any | None:
42
- """
43
- Returns Model if SAT, else None.
44
- """
45
- return res.sat.model.src if res.sat is not None and res.sat.model is not None else None
46
-
47
- def instance_model_src(res: Any) -> str | None:
48
- """
49
- Returns Model.src if SAT, else None.
50
- """
51
- m = instance_model(res)
52
- if m is None:
53
- return None
54
- return getattr(m, "src", None)
55
-
56
-
57
- def instance_model_artifact(res: Any) -> Any | None:
58
- """
59
- Returns Model.artifact if SAT, else None.
60
- """
61
- m = instance_model(res)
62
- if m is None:
63
- return None
64
- return getattr(m, "artifact", None)
65
-
66
-
67
- def instance_unsat_proof(res: Any) -> str | None:
68
- """
69
- Returns Unsat.proof_pp if UNSAT, else None.
70
- """
71
- if instance_status(res) != "unsat":
72
- return None
73
- try:
74
- return res.unsat.proof_pp
75
- except Exception:
76
- return None
77
-
78
-
79
- def instance_unknown_reason(res: Any) -> str | None:
80
- """
81
- Returns unknown StringMsg content if available.
82
- `_utils_pb2.StringMsg` typically stores a `msg` or `value` field; handle both.
83
- """
84
- if instance_status(res) != "unknown":
85
- return None
86
- u = getattr(res, "unknown", None)
87
- if u is None:
88
- return None
89
- return getattr(u, "msg", None) or getattr(u, "value", None) or str(u)
90
-
91
-
92
- def instance_has_errors(res: Any) -> bool:
93
- try:
94
- return len(res.errors) > 0
95
- except Exception:
96
- return False
97
-
98
-
99
- def instance_to_jsonable(res: Any) -> dict[str, Any]:
100
- """
101
- Minimal, stable JSONable view for SpecLogician logs/CLI:
102
- - status
103
- - model.src (if sat)
104
- - unsat.proof_pp (if unsat)
105
- - unknown reason (if unknown)
106
- - errors count (+ optionally stringified errors)
107
- - task (stringified)
108
- """
109
- st = instance_status(res)
110
- out: dict[str, Any] = {
111
- "status": st,
112
- "has_errors": instance_has_errors(res),
113
- "errors_count": len(res.errors) if hasattr(res, "errors") else 0,
114
- }
115
-
116
- if st == "sat":
117
- m = res.sat.model
118
- out["model"] = {
119
- "m_type": str(getattr(m, "m_type", "")),
120
- "src": getattr(m, "src", ""),
121
- # artifact can be huge; include a string repr unless you want deeper extraction
122
- "artifact": str(getattr(m, "artifact", "")),
123
- }
124
- elif st == "unsat":
125
- out["proof_pp"] = getattr(res.unsat, "proof_pp", "")
126
- elif st == "unknown":
127
- out["reason"] = instance_unknown_reason(res)
128
- elif st == "err":
129
- out["reason"] = "err"
130
-
131
- # Task is protobuf; stringify safely
132
- if hasattr(res, "task"):
133
- out["task"] = str(res.task)
134
-
135
- return out
@@ -1,104 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/modeling/complement.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- import hashlib
10
- import json
11
- from typing import Sequence, Iterable
12
-
13
- from pydantic import BaseModel
14
- from imandrax_api_models import RegionStr
15
-
16
-
17
- class ScenarioComplement(BaseModel):
18
- """Result of running region decomposition on all the combined scenarios."""
19
- regions: Sequence[RegionStr]
20
-
21
-
22
- class ScenarioComplementDiff(BaseModel):
23
- """
24
- Difference between two scenario complements (before -> after),
25
- computed using stable fingerprints of regions.
26
-
27
- We store only fingerprints by default (regions can be huge).
28
- """
29
- num_regions_before: int
30
- num_regions_after: int
31
- regions_added: list[str] # fingerprints that appear in after but not before
32
- regions_removed: list[str] # fingerprints that appear in before but not after
33
-
34
-
35
- def _canon_region_str(r: RegionStr) -> str:
36
- """
37
- Produce a deterministic string representation of a RegionStr.
38
-
39
- We prefer a canonical JSON-ish blob rather than str(r) because:
40
- - __repr__ / __str__ may change
41
- - dict ordering may vary
42
- """
43
- # constraints_str is typically a list of strings
44
- constraints = list(getattr(r, "constraints_str", []) or [])
45
-
46
- invariant = getattr(r, "invariant_str", "") or ""
47
- model_eval = getattr(r, "model_eval_str", "") or ""
48
-
49
- # model_str can be dict-like (often {'s': '{x = 0}'}). Canonicalize ordering.
50
- model = getattr(r, "model_str", None)
51
- if isinstance(model, dict):
52
- # stringify values to avoid non-jsonable shapes
53
- model_items = {str(k): str(v) for k, v in sorted(model.items(), key=lambda kv: str(kv[0]))}
54
- elif model is None:
55
- model_items = {}
56
- else:
57
- # fallback: stringify
58
- model_items = {"_": str(model)}
59
-
60
- payload = {
61
- "constraints": constraints,
62
- "invariant": invariant,
63
- "model": model_items,
64
- "model_eval": model_eval,
65
- }
66
-
67
- # separators for stable compact encoding
68
- return json.dumps(payload, sort_keys=True, separators=(",", ":"))
69
-
70
-
71
- def _fingerprint_region(r: RegionStr | str) -> str:
72
- """
73
- Stable-ish fingerprint for a RegionStr (or a raw string region).
74
- """
75
- if isinstance(r, RegionStr):
76
- s = _canon_region_str(r)
77
- else:
78
- s = str(r)
79
-
80
- h = hashlib.sha256(s.encode("utf-8")).hexdigest()
81
- # short prefix is plenty for diffs + UI, but keep full if you prefer
82
- return h[:16]
83
-
84
-
85
- def _fingerprints(regions: Iterable[RegionStr | str]) -> set[str]:
86
- return {_fingerprint_region(r) for r in regions}
87
-
88
-
89
- def diff_scenario_complement(before: ScenarioComplement, after: ScenarioComplement) -> ScenarioComplementDiff:
90
- before_regions = list(before.regions or [])
91
- after_regions = list(after.regions or [])
92
-
93
- bf = _fingerprints(before_regions)
94
- af = _fingerprints(after_regions)
95
-
96
- added = sorted(list(af - bf))
97
- removed = sorted(list(bf - af))
98
-
99
- return ScenarioComplementDiff(
100
- num_regions_before=len(before_regions),
101
- num_regions_after=len(after_regions),
102
- regions_added=added,
103
- regions_removed=removed,
104
- )